Skip to content

fix(ui5-popup): announce invisible messages inside open popups - #13889

Open
ivoplashkov wants to merge 8 commits into
mainfrom
fix/invisible-message-in-dialog
Open

fix(ui5-popup): announce invisible messages inside open popups#13889
ivoplashkov wants to merge 8 commits into
mainfrom
fix/invisible-message-in-dialog

Conversation

@ivoplashkov

Copy link
Copy Markdown
Contributor

Screen readers scope their accessibility tree to a modal popup's subtree while it is open, so announcements written to the default body-level aria-live region are not read out.

InvisibleMessage now supports registering per-container aria-live regions. Popup registers its root as a region on open and deregisters it on close, so announce() routes messages into the open popup and they are heard.

Fixes #13613

Screen readers scope their accessibility tree to a modal popup's
subtree while it is open, so announcements written to the default
body-level aria-live region are not read out.

InvisibleMessage now supports registering per-container aria-live
regions. Popup registers its root as a region on open and deregisters
it on close, so announce() routes messages into the open popup and
they are heard.

Fixes #13613
The test page used absolute cross-package src paths
(/packages/base/src/...ts) which Rollup could not resolve during the
production/test bundle, breaking the build. Use the
window["sap-ui-webcomponents-bundle"] global (as other test pages do)
to access announce(), passing the mode as a plain string.
@sap-ui5-webcomponents-release

Copy link
Copy Markdown

Registering a per-popup aria-live region for every popup rerouted
announcements away from the default body-level region even for
non-modal popovers (e.g. ComboBox suggestion lists), breaking their
existing announcements.

Only modal popups need their own region, since a screen reader scopes
its accessibility tree to a modal popup's subtree. Gate registration on
isModal so non-modal popovers keep using the default region.
@nnaydenow

Copy link
Copy Markdown
Contributor

Please check how the aria-notify polyfil is implemented. Seems like exactly the things we need:

https://github.com/github/arianotify-polyfill/blob/main/arianotify-polyfill.js

@ivoplashkov

Copy link
Copy Markdown
Contributor Author

Please check how the aria-notify polyfil is implemented. Seems like exactly the things we need:

https://github.com/github/arianotify-polyfill/blob/main/arianotify-polyfill.js

Already did, however this is not yet fully available on all browsers and the support is limited. I have also discussed this with the central accessibility team and the overall assumption is that it's still early to adopt. Other than that, this is definitelly the thing we need to solve this case and we should adopt it in future.

Comment thread packages/main/src/Popup.ts
Comment thread packages/main/src/Popup.ts
@ivoplashkov
ivoplashkov deployed to netlify-preview August 11, 2026 05:41 — with GitHub Actions Active
The native <dialog> example called announce() without registering the
dialog as an aria-live region, so announcements were silenced under the
modal a11y scoping. Expose registerInvisibleMessageRegion and
deregisterInvisibleMessageRegion on the test bundle and call them on the
native dialog's open/close so announce() routes into the dialog subtree.
@ivoplashkov
ivoplashkov deployed to netlify-preview August 11, 2026 05:59 — with GitHub Actions Active
Register the in-popup aria-live region only for modal popups. A screen
reader scopes its accessibility tree to a genuinely modal popup (focus
trap / backdrop), which silences the body-level region; non-modal popups
such as the ComboBox dropdown do not cause this scoping, so their
announcements are still heard from the default body-level region and must
not be routed into the popup subtree.

Add native <dialog>, native popover, and native popover + aria-modal
comparison cases to the InvisibleMessageInDialog test page to demonstrate
that modal scoping (not the bare aria-modal attribute) is what silences
the body-level region.
@ivoplashkov
ivoplashkov deployed to netlify-preview August 11, 2026 07:08 — with GitHub Actions Active

this._deregisterResizeHandler();
this._detachBrowserEvents();
this._deregisterInvisibleMessageRegion();

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.

You register it on open and deregister on close, why is this not in the if statement above?

</div>

<h1 style="margin-top: 2rem;">Comparison: native popover (top layer) WITH aria-modal="true"</h1>
<p class="note">

@ndeshev ndeshev Aug 11, 2026

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.

Just a note - JAWS announces the body spans of all popover examples, the only issue is with the native dialog's showModal() behavior

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InvisibleMessage: announcements are dropped by VoiceOver while a modal popup is open

4 participants