Skip to content

[EuiStepsHorizontal][A11y] Prevent screen reader duplication#9574

Open
mgadewoll wants to merge 5 commits intoelastic:mainfrom
mgadewoll:stepshorizontal/a11y-prevent-duplication
Open

[EuiStepsHorizontal][A11y] Prevent screen reader duplication#9574
mgadewoll wants to merge 5 commits intoelastic:mainfrom
mgadewoll:stepshorizontal/a11y-prevent-duplication

Conversation

@mgadewoll
Copy link
Copy Markdown
Contributor

@mgadewoll mgadewoll commented Apr 8, 2026

Summary

This PR updates EuiStepsHorizontal to fix duplicate output for screen readers.
The output for screen readers previously read:

  • title attribute (though this is not consistently read across screen readers)
  • DOM content
    • visible label
    • hidden screen reader text OR icon label (same as title)

Instead of using the title attribute we can use aria-label instead which results in only the aria-label being read for the button element.

⚠️ This means we won't have the browser "tooltip" on hover but considering the cons/pros of that attribute and that we're generally considering to remove it across EUI, this should be reasonable to do. The steps themselves provide all information, the title isn't actually adding further benefit.

API Changes

🟢 No API changes

Screenshots

screen reader output

screen reader Before After
NVDA / Chrome Screenshot 2026-04-08 at 12 10 15 Screenshot 2026-04-08 at 12 09 43
JAWS / Chrome Screenshot 2026-04-08 at 12 05 03 Screenshot 2026-04-08 at 12 07 00

video

screen reader Before After
NVDA / Chrome
Screen.Recording.2026-04-08.at.12.09.49.mov
Screen.Recording.2026-04-08.at.12.09.23.mov
JAWS / Chrome
Screen.Recording.2026-04-08.at.12.04.24.mov
Screen.Recording.2026-04-08.at.12.06.28.mov
VoiceOver / Safari
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.

  • 🔴 Breaking changes — What will break? How many usages in Kibana/Cloud UI are impacted?
  • 💅 Visual changes — May impact style overrides; could require visual testing. Explain and estimate impact.
  • 🧪 Test impact — May break functional or snapshot tests (e.g., HTML structure, class names, default values).
  • 🔧 Hard to integrate — If changes require substantial updates to Kibana, please stage the changes and link them here.

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

  • verify that navigating the component in screen readers (JAWS, NVDA, VO) doesn't result in duplication anymore
  • verify there is no unexpected visual/functional regression with production

Checklist before marking Ready for Review

Reviewer checklist

  • Approved Impact Assessment — Acceptable to merge given the consumer impact.
  • Approved Release Readiness — Docs, Figma, and migration info are sufficient to ship.

@mgadewoll mgadewoll self-assigned this Apr 8, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 title attribute on EuiStepHorizontal’s <button> with aria-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.

Comment thread packages/eui/src/components/steps/step_horizontal.tsx Outdated
Comment on lines 119 to 123
aria-disabled={status === 'disabled' ? true : undefined}
className={classes}
title={titleAttr}
aria-label={titleAttr}
onClick={onStepClick}
disabled={disabled}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@elasticmachine
Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

History

cc @mgadewoll

@elasticmachine
Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

History

cc @mgadewoll

@weronikaolejniczak weronikaolejniczak self-requested a review April 13, 2026 08:30
Copy link
Copy Markdown
Contributor

@weronikaolejniczak weronikaolejniczak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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? 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants