Summary
In a Dialog, mouse clicks cannot focus text inputs/textareas/contenteditable. Keyboard Tab + typing works, and click-driven controls (Close/Submit buttons) work — only fields that rely on mouse focus are dead. Observed on the v1/espresso-tokens branch.
Root cause
Dialog.vue uses the scrollable layout where <DialogContent> is nested inside a scrollable <DialogOverlay> (overflow-y-auto) — the standard pattern for tall, scrollable dialogs.
However, the overlay element ends up with a pointerdown handler bound through Vue's .prevent modifier (confirmed live: the canceling listener's currentTarget is the overlay div, owner Primitive). Because content is nested under the overlay, a pointerdown on a dialog input bubbles up to the overlay and is preventDefault()'d.
Per the Pointer Events spec, cancelling pointerdown suppresses the compatibility mousedown — and mousedown's default action is what focuses an input + places the caret. Net effect:
pointerdown and click fire on the input, but mousedown and focusin never do.
- Inputs/textareas/contenteditable can't be focused by mouse.
- Buttons still work because they act on
click.
Note: reproduces only with a trusted (real) mouse event. Synthetic el.click() / dispatchEvent bypasses the focus-on-mousedown machinery and will not reproduce it.
Open question
I could not locate where the overlay's pointerdown.prevent is bound — it is not in Dialog.vue's template, nor anywhere in reka-ui's Dialog/* source. It arrives on the overlay element via the $attrs forwarding chain (DialogOverlay → DialogOverlayImpl → Primitive). Tracking down and removing it at the source is the proper fix.
Current workaround (shipped)
@pointerdown.stop on <DialogContent> so content pointerdowns never reach the overlay's prevent handler. Verified: input focus, typing, Escape, buttons, and outside-click dismiss all work (reka's dismiss detection is capture-phase, unaffected by a bubble-phase .stop).
Proper fix options
- Find and remove/scope the stray
pointerdown.prevent on the overlay (ideal — root cause).
- Keep the workaround
@pointerdown.stop.
- Restructure to reka's centered sibling layout (
DialogOverlay/DialogContent as siblings, content fixed-centered) — note this trades away in-overlay scrolling for tall dialogs, and a naive sibling scroll-wrapper would render even when closed (DialogPortal is a bare Teleport), so it needs its own Presence gating.
🤖 Generated with Claude Code
Summary
In a
Dialog, mouse clicks cannot focus text inputs/textareas/contenteditable. KeyboardTab+ typing works, andclick-driven controls (Close/Submit buttons) work — only fields that rely on mouse focus are dead. Observed on thev1/espresso-tokensbranch.Root cause
Dialog.vueuses the scrollable layout where<DialogContent>is nested inside a scrollable<DialogOverlay>(overflow-y-auto) — the standard pattern for tall, scrollable dialogs.However, the overlay element ends up with a
pointerdownhandler bound through Vue's.preventmodifier (confirmed live: the canceling listener'scurrentTargetis the overlay div, ownerPrimitive). Because content is nested under the overlay, apointerdownon a dialog input bubbles up to the overlay and ispreventDefault()'d.Per the Pointer Events spec, cancelling
pointerdownsuppresses the compatibilitymousedown— andmousedown's default action is what focuses an input + places the caret. Net effect:pointerdownandclickfire on the input, butmousedownandfocusinnever do.click.Note: reproduces only with a trusted (real) mouse event. Synthetic
el.click()/dispatchEventbypasses the focus-on-mousedown machinery and will not reproduce it.Open question
I could not locate where the overlay's
pointerdown.preventis bound — it is not inDialog.vue's template, nor anywhere in reka-ui'sDialog/*source. It arrives on the overlay element via the$attrsforwarding chain (DialogOverlay→DialogOverlayImpl→Primitive). Tracking down and removing it at the source is the proper fix.Current workaround (shipped)
@pointerdown.stopon<DialogContent>so content pointerdowns never reach the overlay's prevent handler. Verified: input focus, typing, Escape, buttons, and outside-click dismiss all work (reka's dismiss detection is capture-phase, unaffected by a bubble-phase.stop).Proper fix options
pointerdown.preventon the overlay (ideal — root cause).@pointerdown.stop.DialogOverlay/DialogContentas siblings, contentfixed-centered) — note this trades away in-overlay scrolling for tall dialogs, and a naive sibling scroll-wrapper would render even when closed (DialogPortalis a bareTeleport), so it needs its ownPresencegating.🤖 Generated with Claude Code