Toast.Root sets aria-hidden on a focusable element when priority is "high"
- Dominant language
- TypeScript
- Stars
- 10.9k
- Forks
- 543
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 101
Description
Base UI version: 1.8.0
React 19, Next.js 16.3.4
A toast raised with `priority: "high"` renders as a focusable element that is hidden from the accessibility tree. axe reports it as `aria-hidden-focus` at serious severity. A keyboard user can Tab onto the toast, and a screen reader will not describe what they have landed on.
The two attributes come from `Toast.Root` itself, in `toast/root/ToastRoot.js`:
- line 423 sets `role` to `alertdialog` when the toast is high priority, and `dialog` otherwise
- line 424 sets `tabIndex: 0` unconditionally
- line 428 sets `aria-hidden` to `true` when the toast is high priority and the viewport is not focused
So the combination only appears for high priority toasts, and only while the viewport does not have focus.
### Reproduction
The unfocused condition matters, so it is worth stating up front. If you focus the viewport, `aria-hidden` is removed and the violation goes away. The state that needs testing is the ordinary one, where a toast has appeared and nobody has tabbed to it yet.
1. Render `Toast.Provider`, `Toast.Portal` and `Toast.Viewport` in the standard arrangement, mapping toasts to `Toast.Root`.
2. Add a toast with `priority: "high"`.
3. Do not focus the viewport.
4. Inspect the rendered `Toast.Root`. It carries `role="alertdialog"`, `tabIndex="0"` and `aria-hidden="true"` together.
5. Run axe against the page. It reports `aria-hidden-focus`.
### What we expected
An element that is reachable by keyboard should not be hidden from assistive technology. Either the element is exposed while it can be focused, or it is not focusable while it is hidden.
### Notes that may save you time
The message itself is still announced. Base UI renders sibling live regions that carry the toast text, so this is a stray tab stop rather than a silent error. That caps how bad it is, and it is why we are reporting it rather than pinning an older version.
It is not a transient state during the entering animation. The attributes are still present after a 1.5 second settle.
Passing `tabIndex={-1}` to `Toast.Root` does not help, because `Toast.Root` sets the attribute itself. That is the documented workaround for the same defect in Radix (`radix-ui/primitives#2584`), and it does not apply here.
### One consequence worth flagging for other users
Because the root is `aria-hidden` while unfocused, `getByRole` in Playwright and Testing Library cannot see a high priority toast at all. Both resolve against the accessibility tree. Any test written as `getByRole("alertdialog")` will fail for a reason that has nothing to do with the behaviour under test. Addressing the toast by CSS or by text works.
We lost time to this before understanding the cause, so it may be worth a line in the docs whatever happens to the underlying issue.
### Related
This looks like the same shape as #5528 (combobox) and #4678 (dialog), both open and both `aria-hidden` applied to content that stays in the tab order. Filing separately because the source here is `Toast.Root`'s own props rather than an outside-content or background treatment, but they may share a fix.
Contributor guide
Research direction
Start in toast/root/ToastRoot.js around lines 423-428 and reproduce the unfocused high-priority toast state described in the issue. Run axe against the standard Toast.Provider, Toast.Portal, and Toast.Viewport arrangement. Done when the high-priority Toast.Root no longer combines keyboard focusability with aria-hidden and the aria-hidden-focus violation is gone.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, react, typescript
- Domain
- accessibility, frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100