Skip to content

[a11y] Fix reporting flyout screenreader issues#220028

Closed
paulinashakirova wants to merge 6 commits intoelastic:mainfrom
paulinashakirova:a11y-reporting-flyout
Closed

[a11y] Fix reporting flyout screenreader issues#220028
paulinashakirova wants to merge 6 commits intoelastic:mainfrom
paulinashakirova:a11y-reporting-flyout

Conversation

@paulinashakirova
Copy link
Copy Markdown
Contributor

@paulinashakirova paulinashakirova added Project:Accessibility release_note:skip Skip the PR/issue when compiling release notes backport:all-open Backport to all branches that could still receive a release a11y Accessibility issue labels May 4, 2025
@paulinashakirova paulinashakirova self-assigned this May 4, 2025
@paulinashakirova paulinashakirova requested a review from a team as a code owner May 4, 2025 13:35
@elasticmachine
Copy link
Copy Markdown
Contributor

Pinging @elastic/kibana-accessibility (Project:Accessibility)

</EuiText>
</EuiFlyoutHeader>
<EuiFlyoutBody banner={outcomeCallout}>
<EuiFlyoutBody banner={outcomeCallout} aria-live="polite">
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.

What if we move the aria-live attribute to the content of outcomeCallout? Could you also try just setting role="status" for EuiCallOut?

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.

Thank you for these ideas 💡
I tested them 😊
Setting role="status" and/or aria-live="polite" on EuiCallOut without anything set on EuiFlyoutBody didn't work in Safari, Chrome and Firefox.
My assumption it is because we are rendering Callout conditionally so VO doesn't read the content that appears later on in the DOM.
I understand the implication that if we add aria-live for the whole flyout, in case in the future we have more "appearing" things, screenreader will be reading all of them, but in our current case, this callout is the only element that needs to be read dynamically.
What do you think I should do?
I am not sure I have other ideas for now, but I can keep looking 👀 😊
Or we could keep this fix as it is now as an option.

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.

Right, but my suggestion here aims to address that issue globally. What if we move the fix to the EUI side and implement something like this:

image

@elastic/eui-team, would a fix like this be acceptable?

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.

@alexwizp this solution is simple enough and would indeed allow to define the live region directly on the banner as opposed to the whole flyout. The live region node would always be in the DOM, and screen readers would be able to detect the content change inside.

My only concern is that this way we will always render a div, there will be a redundant DOM node for every flyout that gets rendered. Furthermore, it's styled, which could affect visually the rest of the flyout content.

From my perspective, the trade-off is worth it to make the design accessible.

One note, we should try to optimize for NVDA and JAWS. It's worth testing the behavior with them. If you need any help with that, I'm more than happy to assist!

Copy link
Copy Markdown
Contributor Author

@paulinashakirova paulinashakirova Jun 16, 2025

Choose a reason for hiding this comment

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

@alexwizp and @weronikaolejniczak 😊
Thank you for your suggestions/discussion.

I was thinking now - and came up with 2 other ideas... what if we wrap the callout in a div (it is already used this way in group_name.tsx and flash_messages.tsx

     <EuiScreenReaderOnly>
        <div aria-live="polite" role="status">
          {a11yAnnouncement}
        </div>
      </EuiScreenReaderOnly>
return (
    <div aria-live="polite" data-test-subj="FlashMessages">
      {messages.map(({ type, message, description, iconType }, index) => (
        <Fragment key={index}>
          <EuiCallOut
            color={FLASH_MESSAGE_TYPES[type].color}
            iconType={iconType ?? FLASH_MESSAGE_TYPES[type].iconType}
            title={message}
          >

because not all callouts need to be announced, right? Some are static information without a dynamic update.

Or...

For example add an optional prop announceChanges or asStatus, to avoid "overannouncing"...

The first option is faster since we don't need to involve Eui team, but if it could be beneficial for the future - we could proceed with discussing in eui channel...
wdyt?

Copy link
Copy Markdown
Contributor

@mgadewoll mgadewoll Jun 20, 2025

Choose a reason for hiding this comment

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

@alexwizp The downside of adding the banner like this is that it would read any content on update, meaning potentially unwanted content. EuiCallOut for example can have an optional close button or in theory any custom content as children.

toggle screenshot ![Screenshot 2025-06-20 at 17 47 39](https://github.com/user-attachments/assets/b91161dd-62ec-4557-9b12-d6af888c55a0)

Beyond that my concern is that it's not addressing the issue generically enough. It would kind of solve the issue in flyouts but not for any other usages of EuiCallOut (or other announcements) in other, similar places. Imho, we could try to solve this on a more generic level which would then also provide a solution for this specific use case in flyouts.

I've created a rough PoC here, to visualize the idea to:

  • enhance EuiScreenReaderLive to support announcing updates on mount as well by updating the content initially and triggering a re-render (this allows us to work around the limitation of the live region having to be in the DOM already)
  • update EuiCallOut with EuiScreenReaderLive to support screen reader output of its main content (leaving out unwanted content)

This then results in a usage inside EuiFlyout that resolves the original issue:

toggle screen recording
Screen.Recording.2025-06-20.at.19.03.04.mov

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.

@mgadewoll I agree with your approach overall, but I do have one concern: it may be difficult to ensure that EUI users — especially when using EuiFlyoutBody — remember to set disableScreenReaderOutput. Additionally, in most of our similar components, the default value for disableScreenReaderOutput is false, whereas here it's set to true. This change implies that screen reader output is disabled by default, which could be misleading. It might be worth considering a rename to better reflect this behavior.

As for EuiScreenReaderLiveProps, introducing both type and usePortal as separate props could lead to confusion. Would it be possible to simplify this by combining them into a single, more intuitive prop?

Copy link
Copy Markdown
Contributor

@mgadewoll mgadewoll Jun 23, 2025

Choose a reason for hiding this comment

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

@alexwizp That's a valid concern. And the choices are mainly due to it being a quick PoC and me trying to be careful about the impact. 😅
The reason I chose to use disableScreenReaderOutput={true}as default is to prevent potentially interfering with current implementations. EuiCallOut is used in many different places/ways and I don't have an overview of where additional announcements on mount might be an issue. 🤔

As for EuiScreenReaderLiveProps, introducing both type and usePortal as separate props could lead to confusion. Would it be possible to simplify this by combining them into a single, more intuitive prop?

Sure, the API is not finalized. I agree that type is not the most descriptive to what it's doing. What I tried here was to see what variants might be needed first. usePortal most likely can be removed as it's probably always needed to ensure the announcement content is not added next to the visual representation content. Otherwise we'd have duplicate content for screen reader users.

Copy link
Copy Markdown
Contributor Author

@paulinashakirova paulinashakirova Jun 25, 2025

Choose a reason for hiding this comment

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

@mgadewoll Thank you so much for all your help! 🤩
The fix you are proposing looks great!
I will happily wait for the fix on your end when you come to an alignment and will find time to implement it.
I will try to keep an eye on the issue, and if you could tag me or @alexwizp when your PR is in review - would be great 😌

And when you create an issue, could you please mention it here, so I could reference it. 🙏🏻

Copy link
Copy Markdown
Contributor

@mgadewoll mgadewoll Jun 26, 2025

Choose a reason for hiding this comment

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

@paulinashakirova Yes sure, will do! I added an issue on our side for it and we'll refine it on EUI side.

@paulinashakirova paulinashakirova requested a review from alexwizp May 29, 2025 19:17
@elasticmachine
Copy link
Copy Markdown
Contributor

💚 Build Succeeded

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
reporting 110.7KB 110.7KB +81.0B

History

cc @paulinashakirova

@elasticmachine
Copy link
Copy Markdown
Contributor

🤖 Jobs for this PR can be triggered through checkboxes. 🚧

ℹ️ To trigger the CI, please tick the checkbox below 👇

  • Click to trigger kibana-pull-request for this PR!
  • Click to trigger kibana-deploy-project-from-pr for this PR!
  • Click to trigger kibana-deploy-cloud-from-pr for this PR!
  • Click to trigger kibana-renovate-helper for this PR!

@paulinashakirova
Copy link
Copy Markdown
Contributor Author

Closed this PR because the fix will be done on EUI side.

@paulinashakirova
Copy link
Copy Markdown
Contributor Author

The issue is fixed. Can't reproduce anymore

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

Labels

a11y Accessibility issue backport:all-open Backport to all branches that could still receive a release Project:Accessibility release_note:skip Skip the PR/issue when compiling release notes

Projects

None yet

6 participants