Skip to content

JS API

Options

  • Type: Boolean
  • Default: true

Open the dialog as a true modal with showModal(): the platform then handles the focus trap, makes the rest of the page inert, restores focus on close and paints the dialog in the top layer. Set it to false (via data-option-no-modal) to use show() instead, which keeps the rest of the page interactive (useful for a slide-in nav).

html
<dialog data-component="Action Dialog"
  data-option-no-modal>
  ...
</dialog>

trapFocus

  • Type: Boolean
  • Default: true

Trap the tabulation inside the dialog. This is only meaningful on the non-modal path (modal: false): showModal() already traps focus natively, so the option is a no-op when modal is true. On the non-modal path it saves the active element on open, keeps Tab inside the dialog while open, and restores focus on close.

html
<dialog data-component="Action Dialog"
  data-option-no-modal
  data-option-no-trap-focus>
  ...
</dialog>

scrollLock

  • Type: Boolean
  • Default: true

Lock the scroll on the document element while the dialog is open by toggling document.documentElement.style.overflow. Set it to false with data-option-no-scroll-lock to leave the page scrollable.

Properties

dialog

  • Type: HTMLDialogElement

A getter returning the native <dialog> element (this.$el).

transitions

  • Type: Array<Transition | ViewTransition>

A getter returning every Transition and ViewTransition child the dialog orchestrates.

Methods

open

  • Returns Promise<void>

Open the dialog: call showModal() (or show() when modal is false), lock the scroll, emit open, then run every child's enter() alongside the waitUntil extensions. A no-op if the dialog is already open. Resolves once the enter choreography has finished.

close

  • Returns Promise<void>

Close the dialog: emit close, run every child's leave() alongside the waitUntil extensions, then call dialog.close(), release the focus-trap (non-modal path) and unlock the scroll. A no-op if the dialog is already closed, and concurrent calls await the same run. Resolves once closed.

toggle

  • Returns Promise<void>

Call close() if the dialog is open, open() otherwise.

Events

Both lifecycle events bubble up the DOM tree, so ancestors can route them and any listener up the tree can join the choreography.

open

Emitted when the dialog starts opening, before the enter transitions run. The detail carries a waitUntil function.

close

Emitted when the dialog starts closing, before the leave transitions run. The detail carries a waitUntil function.

Extending the choreography with waitUntil

Both lifecycle events expose event.detail.waitUntil(x), modeled on the Service Worker ExtendableEvent. Any listener can register an extension while the event dispatches, and the dialog awaits it alongside its transition children — on close, the native dialog stays painted until every registered extension settles. This lets any component participate in the open and close choreography without being a declared child.

waitUntil() accepts either form:

  • a promise (any thenable), awaited as is
  • a transitioner: a duck-typed object with enter() and leave() methods, e.g. MotionView from @studiometa/ui-motion — the dialog follows its lifecycle by awaiting enter() on open and leave() on close

For example, a Motion box can spring in and out with the dialog through two Action attributes:

html
<dialog
  data-component="Action Dialog"
  data-on:open="Motion(#box)->event.detail.waitUntil(target.play())"
  data-on:close="Motion(#box)->event.detail.waitUntil(target.reverse())"
  data-on:cancel.prevent="Dialog.close()">

</dialog>

With the transitioner form, a single handler is enough — pass the transitioner itself and the dialog picks the right phase, like a MotionView navigation panel animating out before the dialog hides:

html
<dialog
  data-component="Action Dialog"
  data-on:close="MotionView(#nav)->event.detail.waitUntil(target)"
  data-on:cancel.prevent="Dialog.close()">
  <nav id="nav">…</nav>
</dialog>

Registration is only valid synchronously, while the event dispatches; later calls warn and are ignored. A rejected extension never blocks the choreography: the rejection is reported with a warning and the run completes.