Dismissable Layer
Headless Dismissable Layer; covers Radix DismissableLayer, React Aria overlay dismissal, Reka UI DismissableLayer.
| Package | @vizejs/ui/dismissable-layer |
| Maturity | stable |
| Own the source | vize lib pull dismissable-layer |
| Requires | — |
| Aliases | dismissible layer, outside interaction, escape routing, overlay stack |
| Covers | Radix DismissableLayer, React Aria overlay dismissal, Reka UI DismissableLayer |
API
createDismissableLayer
Create an SSR-safe, document-scoped dismissal layer for overlays and popups.
function createDismissableLayer( options: DismissableLayerOptions, ): DismissableLayerController
useDismissableLayer
Create, mount-activate, and scope-dispose a dismissable overlay layer.
function useDismissableLayer(options: DismissableLayerOptions): DismissableLayerController
eventElement
Resolve an event target without assuming it comes from the current realm.
function eventElement(value: EventTarget | null): Element | null
readBoolean
function readBoolean(source: unknown, name: string, fallback: boolean): boolean
readRoot
function readRoot(source: unknown): Element | null
readBranches
function readBranches(source: unknown, document: Document | null): readonly Element[]
validateBranches
function validateBranches(source: readonly unknown[], document: Document | null): Element[]
validateOptions
function validateOptions(options: DismissableLayerOptions): void
createPointerDownOutsideEvent
Build an immutable outside pointer event that is stable after native dispatch mutates.
function createPointerDownOutsideEvent( originalEvent: PointerEvent | MouseEvent | TouchEvent, target: Element, ): DismissableLayerPointerDownOutsideEvent
createFocusOutsideEvent
Build an immutable outside focus event that preserves related-target evidence.
function createFocusOutsideEvent( originalEvent: FocusEvent, target: Element, ): DismissableLayerFocusOutsideEvent
createEscapeKeyDownEvent
Build an immutable Escape event before dismissal is committed.
function createEscapeKeyDownEvent( originalEvent: KeyboardEvent, target: Element | null, ): DismissableLayerEscapeKeyDownEvent
createDismissEvent
Build the final immutable dismissal notification.
function createDismissEvent( reason: DismissableLayerDismissReason, originalEvent: Event, target: Element | null, ): DismissableLayerDismissEvent
recomputeDismissableLayers
function recomputeDismissableLayers(document: Document): void
attachDismissableLayer
function attachDismissableLayer(token: DismissableLayerToken, root: Element): void
detachDismissableLayer
function detachDismissableLayer(token: DismissableLayerToken): void
refreshDismissableLayer
function refreshDismissableLayer(token: DismissableLayerToken): void
Behavior
The dismissable layer primitive is a document-scoped overlay foundation. It does not render a component shell or visual styling; consumers keep ownership of source, markup, Teleport placement, inerting, focus guards, and scroll locks while sharing a deterministic dismissal stack.
| Scenario | Required behavior |
|---|---|
| Activation | activate() joins the root element's Document stack when root resolves to a connected Element; deactivate() and dispose() release ownership exactly once. |
| Top-layer routing | Only the last activated, connected, enabled layer in a document is topmost and eligible for outside pointer, outside focus, and Escape dismissal. |
| Branches | Reactive branches and imperative registerBranch() roots are treated as inside the layer, including portalled content and composed Shadow DOM paths. |
| Outside pointer | A pointer down, mouse down fallback, or touch start outside the top layer emits immutable pointer-down-outside evidence, then onInteractOutside, then onDismiss unless prevented or a callback throws. |
| Outside focus | A focusin outside the top layer emits immutable focus-outside evidence with relatedTarget, then onInteractOutside, then onDismiss unless prevented or a callback throws. |
| Escape routing | An unprevented, non-composing Escape keydown in the document emits immutable escape-key evidence and then onDismiss unless prevented or a callback throws. |
| Prevention | Calling preventDefault() on pointer, focus, or Escape evidence prevents onDismiss for that native event without mutating the original event. |
| Reactive enablement | enabled, outsidePointerDown, outsideFocus, and escapeKey are re-read for every event and during synchronous option refreshes. |
| Root migration | Moving a root within the same document preserves stack order; moving across documents releases old listeners before attaching to the new document. |
| Disconnected roots | Disconnecting a top layer removes its topmost eligibility on the next mutation turn and restores the next eligible parent layer. |
| SSR and hydration | useDismissableLayer() renders deterministic inactive props on the server, touches no global document during render, and activates after client mount without replacing hydrated nodes. |
| Runtime diagnostics | Invalid roots, branches, booleans, callbacks, out-of-scope usage, and post-disposal calls throw stable VIZE_UI_DISMISSABLE_LAYER_* diagnostics. |