Guide

Behavior options

Modal mode, close triggers, scroll lock, focus trap.

Behavior options

By default modals work as proper ARIA dialogs in modal mode: focus trap, body inert, scroll locked, close on Esc, close on overlay click. You opt out of pieces you don't need.

The five flags

All available on <ModalTarget> (per-target), createModal({ groups: { x: {...} } }) (per-group), or both — props override the registered config.

FlagDefaultEffect when true
enableInteractOutsidefalseSwitch to non-modal mode: no focus trap, body NOT inert, background text-selectable, aria-modal="false".
disableCloseOnInteractOutsidefalseClick outside the <ModalTarget> region does NOT close. (Relevant when the region is smaller than viewport.)
disableCloseOnInteractOverlayfalseClick in the empty area around the card (inside the region) does NOT close.
disableLockBodyScrollfalseBody scroll is NOT locked.
disableCloseOnEscapefalsePressing Esc does NOT close the topmost modal of the group.

The asymmetric prefixes (enable* vs disable*) are intentional: every flag's false matches the modal-mode default. You only ever set a flag to true to opt out.

Default behavior in detail

When all flags are at their false defaults:

  • Focus trap — Tab and Shift+Tab cycle within the dialog.
  • Body inert — background elements are not focusable; assistive tech sees only the dialog.
  • body { overflow: hidden } + paddingRight compensation for vanished scrollbar.
  • body { user-select: none } — text selection blocked outside the modal (released on close).
  • Esc closes the topmost modal of any active group.
  • Click outside card closes — both inside and outside the region (when applicable).

Common configurations

Confirmation dialog (strict)

User must explicitly choose — no Esc, no outside-click escape.

createModal({
  groups: {
    confirm: {
      disableCloseOnEscape: true,
      disableCloseOnInteractOverlay: true,
      disableCloseOnInteractOutside: true
    }
  }
})

Side panel (non-modal)

Doesn't trap focus, doesn't lock scroll — used like a navigator on the side.

createModal({
  groups: {
    panel: {
      enableInteractOutside: true,
      disableLockBodyScroll: true
    }
  }
})

Per-target override

<!-- Group base config from createModal applied -->
<ModalTarget group="confirm" />

<!-- Same group, Esc re-enabled for this specific target -->
<ModalTarget :disableCloseOnEscape="false" group="confirm" />

Reading effective options inside a modal

useModalContext() returns effectiveOptions in the positive form for ergonomic reading:

const { effectiveOptions } = useModalContext()

effectiveOptions.value.interactOutside // boolean
effectiveOptions.value.closeOnInteractOutside // boolean
effectiveOptions.value.closeOnInteractOverlay // boolean
effectiveOptions.value.lockBodyScroll // boolean
effectiveOptions.value.closeOnEscape // boolean

The translation negative→positive happens once at <ModalTarget> boundary.

Custom close guards

Block close attempts from inside the modal via onBeforeClose — return false to veto. Callers can bypass guards with { ignoreGuard: true }. See Modal context for the full pattern.

Copyright © 2026