[EuiStepsHorizontal][A11y] Prevent screen reader duplication#9574
[EuiStepsHorizontal][A11y] Prevent screen reader duplication#9574mgadewoll wants to merge 5 commits intoelastic:mainfrom
Conversation
- this prevents reading out duplicate text content
There was a problem hiding this comment.
Pull request overview
This PR improves EuiStepsHorizontal / EuiStepHorizontal screen reader output by replacing the title attribute with aria-label to prevent duplicate announcements.
Changes:
- Replaced the
titleattribute onEuiStepHorizontal’s<button>witharia-label. - Updated Jest snapshots to reflect the new attribute output.
- Added an upcoming changelog entry documenting the accessibility fix.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/eui/src/components/steps/step_horizontal.tsx | Switches from title to aria-label on the step button to prevent duplicate SR output |
| packages/eui/src/components/steps/snapshots/steps_horizontal.test.tsx.snap | Snapshot updates for EuiStepsHorizontal button attributes |
| packages/eui/src/components/steps/snapshots/step_horizontal.test.tsx.snap | Snapshot updates for EuiStepHorizontal button attributes |
| packages/eui/changelogs/upcoming/9574.md | Changelog entry for the accessibility improvement |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| aria-disabled={status === 'disabled' ? true : undefined} | ||
| className={classes} | ||
| title={titleAttr} | ||
| aria-label={titleAttr} | ||
| onClick={onStepClick} | ||
| disabled={disabled} |
There was a problem hiding this comment.
aria-disabled is set based on status === 'disabled', but the actual disabled behavior is controlled only by the disabled prop (disabled={disabled} and onStepClick only guards on disabled). This allows a status="disabled" step to still be clickable/activatable despite being announced as disabled. Consider using a single computed boolean (e.g., isDisabled = disabled || status === 'disabled') for aria-disabled, the disabled attribute, and the click guard so behavior matches the a11y state.
There was a problem hiding this comment.
It's not due to the changes in this PR. There might be benefit to having the button focusable if disabled, so I wouldn't change the aria-disabled vs disabled behavior here at this point. What we can do though is guard the onClick on both to prevent it from being being fired if only status === 'disabled' is passed without disabled.
Updated in 0fa990a
- additionally renames the map to fit aria-label usage better
💚 Build SucceededHistory
cc @mgadewoll |
💚 Build Succeeded
History
cc @mgadewoll |
weronikaolejniczak
left a comment
There was a problem hiding this comment.
Tested with NVDA/Chrome, JAWS/Chrome and VO/Safari. There's no duplication ✅
2 doubts (one in a separate comment):
❓ I'm all for removing the title attr but I'm wondering if we should replace it with an EuiToolTip or not... What do you think?
| event: ReactMouseEvent<HTMLButtonElement, MouseEvent> | ||
| ) => { | ||
| if (!disabled) onClick(event); | ||
| if (!disabled && status !== 'disabled') onClick(event); |
There was a problem hiding this comment.
Before, when using status === 'disabled' onClick would still be triggered which does not make sense but is a behavioral change that we didn't mention in the PR description/changelog, and can potentially break some cases if they rely on it? 🤔
Summary
This PR updates
EuiStepsHorizontalto fix duplicate output for screen readers.The output for screen readers previously read:
titleattribute (though this is not consistently read across screen readers)title)Instead of using the
titleattribute we can usearia-labelinstead which results in only thearia-labelbeing read for the button element.titleisn't actually adding further benefit.API Changes
🟢 No API changes
Screenshots
screen reader output
video
Screen.Recording.2026-04-08.at.12.09.49.mov
Screen.Recording.2026-04-08.at.12.09.23.mov
Screen.Recording.2026-04-08.at.12.04.24.mov
Screen.Recording.2026-04-08.at.12.06.28.mov
Screen.Recording.2026-04-08.at.12.19.22.mov
Screen.Recording.2026-04-08.at.12.18.55.mov
Impact Assessment
Note: Most PRs should be tested in Kibana to help gauge their Impact before merging.
Impact level: 🟢 Low - Due to DOM changes, snapshots might need updating.
Release Readiness
Documentation: {link to docs page(s)}Figma: {link to Figma or issue}Migration guide: {steps or link, for breaking/visual changes or deprecations}Adoption plan (new features): {link to issue/doc or outline who will integrate this and where}QA instructions for reviewer
Checklist before marking Ready for Review
QA: Tested in CodeSandbox and KibanaQA: Tested docs changesBreaking changes: Addedbreaking changelabel (if applicable)Reviewer checklist