Never aria-hidden Something Focusable
NEVER: Put `aria-hidden="true"` (or `role="presentation"`) on a visible focusable element or a subtree that is still tabbable — it strips the a11y tree but NOT the tab order, so focus walks into a ghost: the ring travels off screen and the screen reader announces nothing. Use `inert` on the container (removes it from tab order, a11y tree and hit-testing at once) or unmount it.
Use inert or unmount to hide a subtree, so the tab order and the a11y tree agree
// ghost: <div aria-hidden="true" className="opacity-0"><button>Close</button></div>
<div inert={!open} className="opacity-0"><button>Close</button></div>Bad
Good
Why it matters
aria-hidden="true" removes an element and its whole subtree from the accessibility tree. It does NOT remove anything from the tab order — those are two different trees, and this is the single most common way to desynchronise them. An off-canvas drawer hidden with aria-hidden plus opacity: 0 and a transform is still fully tabbable: keyboard focus walks into it, the focus ring travels off screen, and a screen reader announces nothing whatsoever, because the element it is focused on does not exist as far as the accessibility tree is concerned.
Focusing on "nothing" is a dead end no user can recover from. The correct tools remove the subtree from BOTH trees at once: unmount it, or set the inert attribute on the container — inert takes the subtree out of the tab order, out of the accessibility tree, and out of hit-testing in one attribute, which is why it also shows up in this corpus as the way to freeze the rest of the page during a drag.
If for some reason you must keep aria-hidden, you have to strip tabindex and disable every focusable descendant by hand, and keep doing so as the subtree changes — which is precisely the bookkeeping inert exists to delete.