packages/components — verified on 0.2.321 (newest we have installed) and also present in 0.2.252.
Dialog's README lists under Features:
- Focus trap
- Keyboard support (ESC to close)
- Accessible (WAI-ARIA)None of the first or third is implemented. Grepping the entire src/ui/dialog/ directory for any focus or scroll management returns nothing:
$ grep -rniE 'focus|trap|scrollLock|inert|activeElement|tabindex' src/ui/dialog/
README.md:114:- Focus trap <- the claim
DialogPanel.stx:5: 'fixed inset-0 overflow-y-auto' <- unrelated, a class stringThat is the whole result across Dialog.stx, DialogBackdrop.stx, DialogPanel.stx, DialogTitle.stx, DialogDescription.stx.
What is actually missing
Dialog.stx in full is a :show toggle plus a keydown handler:
<div
:show="isOpen()"
role="dialog"
aria-modal="true"
@keydown="onBackdropKey($event)"
>
<slot />
</div>- No focus trap. Tab walks straight out of the dialog and into the page behind it.
- No scroll lock.
document.bodykeepsoverflow: visible, so the page scrolls under the overlay. - No focus restore. Focus does not return to the trigger on close.
- No initial focus. Nothing moves focus into the panel when it opens.
- Escape is bound to the dialog element, not the document.
@keydownon the root div only fires once focus is already inside — and since nothing moves focus there on open, in the common case (click a trigger, dialog appears) Escape does nothing at all. Points 4 and 5 compound: each makes the other unreachable. aria-modal="true"is asserted without containment. That attribute tells assistive technology the rest of the page is inert. With no focus trap it is not, so the attribute is an active misstatement to AT rather than a missing feature — arguably worse than omitting it.
Why this matters beyond a11y polish
We hit this in an HQ app on a confirmation dialog that deletes a project and all of its log entries. Six Tab presses walked focus out of the dialog and onto the page behind it, while the page scrolled under the overlay. For a destructive confirmation that is the wrong behaviour to ship, so we kept a hand-rolled modal with useScrollLock, a useRef panel, an explicit focus trap and focus restore.
The cost of the gap is not that we wrote a modal. It is that adopting <Dialog> would have removed our working document-level Escape handler and added none of what it advertises — so the component is currently a downgrade from hand-rolled markup for any dialog that matters, which is the opposite of what a component library is for.
Suggested fix
Ideally all of it lands in Dialog so callers get it by default:
- move the Escape listener to
documentwhile open, removing it on close (and on unmount) - focus the panel, or the first tabbable element in it, on open
- trap Tab/Shift-Tab within the panel
- restore focus to the previously-focused element on close
- lock body scroll while open
If any of that is deliberately left to the caller, the README's Features list should say so rather than claiming a focus trap, and aria-modal="true" should be conditional on the trap actually being present.
Happy to send a PR if the approach is agreed — we already have a working implementation of exactly this in an app and would rather delete it than maintain it.