Skip to content

[EuiSelectable] Fix repeated screen reader announcement of results count#9555

Merged
mgadewoll merged 7 commits intoelastic:mainfrom
yansavitski:fix/selectable-screen-reader-repeated-announcement
Apr 15, 2026
Merged

[EuiSelectable] Fix repeated screen reader announcement of results count#9555
mgadewoll merged 7 commits intoelastic:mainfrom
yansavitski:fix/selectable-screen-reader-repeated-announcement

Conversation

@yansavitski
Copy link
Copy Markdown
Contributor

Summary

API Changes

No API changes.

component / parent prop / child change description

Screenshots

N/A — no visual changes, this is a screen reader behavior fix.

Impact Assessment

  • 🔴 Breaking changes
  • 💅 Visual changes
  • 🧪 Test impact — None. All 32 existing tests pass, 14 snapshots match.
  • 🔧 Hard to integrate

Impact level: 🟢 None

Release Readiness

  • Documentation
  • Figma
  • Migration guide
  • Adoption plan

QA instructions for reviewer

Reproducing with NVDA (Windows)

  1. Open the EuiSelectable → With Search story in Storybook
  2. Enable NVDA screen reader
  3. Focus the search input, then navigate options with arrow keys
  4. Before fix: NVDA announces "X results available" before every option
  5. After fix: NVDA announces the results count only once when entering the list, and again only if the count changes (e.g. when filtering via search)

Verifying on macOS via DOM inspection

The root cause is that aria-live regions mutate on every arrow key press, which NVDA picks up and announces. This can be verified without NVDA by observing DOM mutations directly. Open the story in an iframe (/iframe.html?id=forms-euiselectable--with-search) and paste in DevTools Console:

const observer = new MutationObserver((mutations) => {
  mutations.forEach((m) => {
    const el = m.target;
    if (el.getAttribute?.('aria-live') || el.closest?.('[aria-live]')) {
      const liveEl = el.closest?.('[aria-live]') || el;
      console.log(
        `[aria-live="${liveEl.getAttribute('aria-live')}"]`,
        `hidden=${liveEl.getAttribute('aria-hidden')}`,
        `text="${liveEl.textContent?.trim().slice(0, 80)}"`
      );
    }
  });
});
observer.observe(document.body, {
  childList: true, subtree: true, characterData: true,
  attributes: true, attributeFilter: ['aria-hidden']
});
  • Before fix: Console logs aria-live toggles on every arrow key press
  • After fix: No aria-live logs during arrow navigation; logs appear only when typing in search (results count changes)

Checklist before marking Ready for Review

  • Filled out all sections above
  • QA: Tested with VoiceOver (macOS) and verified aria-live region behavior via MutationObserver
  • QA: Tested in Kibana
  • Tests: All existing tests pass
  • Changelog: Added changelog entry
  • Breaking changes

Pass a string instead of a React element to EuiScreenReaderLive by
using EuiI18n render prop pattern. Previously, each re-render created
a new React element reference for listScreenReaderStatus, causing
EuiScreenReaderLive's useEffect to fire on every arrow key navigation
and repeatedly announce the results count via aria-live region.
Strings are compared by value, so the announcement now only triggers
when the count actually changes.

Closes elastic#9197
Copilot AI review requested due to automatic review settings April 2, 2026 11:09
@yansavitski yansavitski requested a review from a team as a code owner April 2, 2026 11:09
@yansavitski yansavitski added the community contribution (Don't delete - used for automation) label Apr 2, 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 updates EuiSelectable’s live screen reader status announcement to avoid repeatedly announcing the total results count during keyboard navigation, addressing #9197 by ensuring EuiScreenReaderLive only re-announces when the count actually changes.

Changes:

  • Reworked the results-count live region content from a React element (<EuiI18n />) to an EuiI18n render-prop string to prevent EuiScreenReaderLive’s [children] effect from firing on unrelated re-renders.
  • Added an upcoming changelog entry for the accessibility bug fix.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
packages/eui/src/components/selectable/selectable.tsx Changes the live-region “results available” announcement to pass a stable string instead of a new React element per render.
packages/eui/changelogs/upcoming/9197.md Documents the EuiSelectable screen reader announcement bug fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/eui/src/components/selectable/selectable.tsx
@yansavitski yansavitski added the skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation) label Apr 2, 2026
@JasonStoltz
Copy link
Copy Markdown
Member

Hey @yansavitski. I'm removing the Community Contribution label. We use that for contributions from outside of Elastic. I'm also removing the skip-changelong label, as this is a change and requires a changelog.

@JasonStoltz JasonStoltz removed skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation) community contribution (Don't delete - used for automation) labels Apr 3, 2026
Comment thread packages/eui/changelogs/upcoming/9555.md
@weronikaolejniczak weronikaolejniczak requested a review from a team April 7, 2026 10:50
@mgadewoll mgadewoll self-requested a review April 8, 2026 07:23
@mgadewoll
Copy link
Copy Markdown
Contributor

Sorry for the delay on this, I had to look at this a bit more, as the duplication in screen readers is not reproducible in EUI, only in Kibana.

That being said, I don't have a clearer answer to why the duplication only happens in Kibana. The most logical explanation imho is that the DOM changes on aria-live in EuiScreenReaderLive are picked up sometimes depending on the time frame in which the DOM change happens, which Kibana might affect differently than on EUI side.

Overall, the proposed solution to stabilize the content passed to EuiScreenReaderLive is correct. If the actual text doesn't change, we don't need to trigger an update in EuiScreenReaderLive.
Generally I'd prefer to memoize the content instead, but that is best done once the component is migrated to a function component as that will make it easier to memoize those contents. So the change to pass string here instead is good enough for now. 👍

@yansavitski yansavitski force-pushed the fix/selectable-screen-reader-repeated-announcement branch from 1d282c4 to 739b373 Compare April 13, 2026 10:13
Comment thread packages/eui/src/components/selectable/selectable.test.tsx Outdated
Comment thread packages/eui/src/components/selectable/selectable.test.tsx Outdated
Comment thread packages/eui/changelogs/upcoming/9555.md Outdated
Copy link
Copy Markdown
Contributor

@mgadewoll mgadewoll left a comment

Choose a reason for hiding this comment

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

🟢 The changes are LGTM and the update works as expected (tested on EUI as well as in Kibana). Thanks for digging into this and contributing to add a solution! 🎉

@elasticmachine
Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

History

@elasticmachine
Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

History

@mgadewoll mgadewoll merged commit 0e8b0fc into elastic:main Apr 15, 2026
5 checks passed
@yansavitski yansavitski deleted the fix/selectable-screen-reader-repeated-announcement branch April 15, 2026 10:33
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.

[EuiSelectable] Remove duplicated state instructions for a11y [AI Connector] (Accessibility): Total number of options announced before each option

6 participants