Skip to Content

Popups

How screen readers read popup content and reading order.

A screen reader is software that reads the page out loud and lets people navigate with the keyboard (and sometimes a braille display). It does not “see” the layout the way sighted users do. It walks through the page in a sequence that usually matches the order of elements in the DOM—roughly, the order nodes appear in the HTML tree.

That matters for popups: if the popup’s markup is far away from the control that opened it in the DOM, the screen reader may read a lot of other page content before it reaches the popup. The user might not realize the popup is there, or they might hear unrelated content mixed in with the popup experience.

Moving keyboard focus into the popup when it opens helps people continue interacting, but it does not change that underlying reading sequence. So focus management and reading order are related problems; you often need to address both.

None of these examples use focus traps. For modal dialogs with an overlay and focus trap, use the Modal component instead.

useInitialFocus: When the popup opens, each example moves focus into the popup (often to a Close control or another safe first stop). That matters because many screen readers only announce new content when focus moves. If focus stays on the trigger, the user may get no cue that a popup appeared. When choosing not to use useInitialFocus, consider the following:

  • Use aria-expanded={true | false} on Popup.Target so assistive tech can report whether the popup is open or closed.
  • Use aria-haspopup="dialog" on Popup.Target as a hint that the control opens a dialog. Caveat: some older screen readers do not understand the "dialog" value. They may treat it like a generic popup and announce “menu” even when you built a dialog. For that reason, we strongly recommend testing with your supported browsers and screen reader combinations during development.

1. Inline popup with portal={false}

Set portal={false} on Popup.Popper so the popup renders in the DOM next to its target. Reading order follows document order. Use useInitialFocus, useReturnFocus, and the usual close hooks. Tradeoff: the popup is constrained by ancestor overflow and positioning context.

2. Reading order with aria-owns

You can keep the default portal (content at the bottom of body) and still try to re-parent the popup in the accessibility tree: add a sibling element after Popup.Target and set aria-owns to the id of the portaled Popup.Card. Some assistive technologies will then treat that card as “owned” by the trigger for browsing and announcements.

Tradeoffs:

  • Support for aria-owns varies. Do not assume every combination of browser and screen reader will honor it the same way.
  • Tab order still follows the real DOM. aria-owns does not move focus targets. You may still need helpers like useFocusRedirect so keyboard users can reach the popup predictably.
  • Combine with useInitialFocus so opening the popup still moves focus and gives a clear announcement where supported.

The Canvas Kit Dialog builds this pattern in.

Another aria-owns example: Table Patterns > Filterable Column Headers.