feat: add label step option for the dialog's accessible name - #3482
feat: add label step option for the dialog's accessible name#3482chuckcarpenter wants to merge 1 commit into
label step option for the dialog's accessible name#3482Conversation
A step with no `title` gets no naming attribute at all on its `<dialog>`, so the dialog has no accessible name and screen readers announce it without one. Steps whose content is text-only had no way to fix that short of adding a visible `title` and changing the UI. Add an optional `label` step option, emitted as `aria-label` on the dialog only when the step has no `title`. It accepts a string or a function returning one, matching `title` and the existing `label` options on buttons and the cancel icon. The function is invoked with the step as `this`, and only when its result will actually be used, so a `label` that throws (an i18n catalog that has not loaded yet, say) cannot break a titled step. When both `title` and `label` are given, `title` wins: `aria-labelledby` outranks `aria-label` in the accessible name computation, so emitting both would leave `label` silently dead, and it keeps the accessible name matching the visible heading (WCAG 2.5.3). The `title` path is untouched and its output is byte-identical. The resolved value is gated on its trimmed length before the attribute is set, so `label: ''`, `label: () => ''` and whitespace-only values omit `aria-label` rather than writing a name that assistive technology treats as empty. The untrimmed string is what gets written. Non-string values are ignored rather than coerced, so a stray plain-JS `label` cannot produce an `[object Object]` accessible name. Note on the original report: #2390 pastes a `<div role="dialog">`, which is pre-v15 markup. v15 renders a native `<dialog>` and sets no `role` attribute anywhere, so the specific axe rule cited in that report no longer selects this element. The underlying complaint is still real and is what this fixes — an unnamed dialog is an unnamed dialog regardless of which linter notices. Every existing step is unaffected: `applyAttrs` skips null, so the DOM for a step without `label` is byte-identical. The one behavior change is for a plain-JS consumer who was already passing an inert `label` key; on a title-less step that key now becomes an accessible name. Fixes #2390 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThe Step API now supports accessible labels for dialogs without visible titles. Labels may be strings or functions, are overridden by titles, and are omitted when empty or whitespace-only. Rendering, unit tests, Cypress tests, and usage documentation cover the behavior. ChangesAccessible Step Labels
Estimated code review effort: 3 (Moderate) | ~20 minutes Mergeability Score: ⚪ Minimal · up to This localized change adds an optional accessible name for title-less dialogs while preserving existing title behavior; no actionable merge-blocking risk remains after normal checks and review. Sequence Diagram(s)sequenceDiagram
participant StepOptions
participant ShepherdElement
participant DialogDOM
StepOptions->>ShepherdElement: provide label or label function
ShepherdElement->>ShepherdElement: resolve label when title is absent
ShepherdElement->>DialogDOM: set valid aria-label
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
shepherd.js/test/cypress/integration/a11y.cy.js(node:2) ESLintIgnoreWarning: The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignore-files Oops! Something went wrong! :( ESLint: 10.8.1 A config object is using the "root" key, which is not supported in flat config system. Flat configs always act as if they are the root config file, so this key can be safely removed. shepherd.js/test/unit/components/shepherd-element.spec.js(node:2) ESLintIgnoreWarning: The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignore-files Oops! Something went wrong! :( ESLint: 10.8.1 A config object is using the "root" key, which is not supported in flat config system. Flat configs always act as if they are the root config file, so this key can be safely removed. shepherd.js/test/unit/step.spec.js(node:2) ESLintIgnoreWarning: The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignore-files Oops! Something went wrong! :( ESLint: 10.8.1 A config object is using the "root" key, which is not supported in flat config system. Flat configs always act as if they are the root config file, so this key can be safely removed. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Coverage Impact This PR will not change total coverage. Modified Files with Diff Coverage (1)
🛟 Help
|

A step with no
titlegets no naming attribute at all on its<dialog>, sothe dialog has no accessible name and screen readers announce it without one.
Steps whose content is text-only had no way to fix that short of adding a
visible
titleand changing the UI.Add an optional
labelstep option, emitted asaria-labelon the dialogonly when the step has no
title. It accepts a string or a functionreturning one, matching
titleand the existinglabeloptions on buttonsand the cancel icon. The function is invoked with the step as
this, andonly when its result will actually be used, so a
labelthat throws (an i18ncatalog that has not loaded yet, say) cannot break a titled step.
When both
titleandlabelare given,titlewins:aria-labelledbyoutranks
aria-labelin the accessible name computation, so emitting bothwould leave
labelsilently dead, and it keeps the accessible name matchingthe visible heading (WCAG 2.5.3). The
titlepath is untouched and itsoutput is byte-identical.
The resolved value is gated on its trimmed length before the attribute is
set, so
label: '',label: () => ''and whitespace-only values omitaria-labelrather than writing a name that assistive technology treats asempty. The untrimmed string is what gets written. Non-string values are
ignored rather than coerced, so a stray plain-JS
labelcannot produce an[object Object]accessible name.Note on the original report: #2390 pastes a
<div role="dialog">, which ispre-v15 markup. v15 renders a native
<dialog>and sets noroleattributeanywhere, so the specific axe rule cited in that report no longer selects this
element. The underlying complaint is still real and is what this fixes — an
unnamed dialog is an unnamed dialog regardless of which linter notices.
Every existing step is unaffected:
applyAttrsskips null, so the DOM for astep without
labelis byte-identical. The one behavior change is for aplain-JS consumer who was already passing an inert
labelkey; on a title-lessstep that key now becomes an accessible name.
Fixes #2390
Verification
shepherd-element.spec.js), Cypress a11y green, lint / prettier /types:checkclean.labelfunction is invoked only when its result is used, and that the trimmed-length gate rejects'',() => '', and whitespace-only values.Worth flagging for review: the axe rule quoted in #2390 (
aria-dialog-name) targets[role="dialog"], and v15 sets noroleattribute anywhere —git grep role -- shepherd.js/srcreturns nothing. That markup came from the pre-v15 Svelte<div>. The underlying problem is still real (a native<dialog>with no accessible name), but the specific rule cited in the report no longer selects this element, so this isfeat:rather than an a11y bug fix.🤖 Generated with Claude Code
Summary by CodeRabbit
labelfor Shepherd steps without visible titles, improving dialog accessibility.