Alert Dialog

A dialog that requires a user response to proceed. It does not close on backdrop click. The trigger and close match Button. A cta close uses the stronger wash step. The popup has no stroke. The title and description are Light.

Anatomy

<UiAlertDialogRoot>
  <UiAlertDialogTrigger>Open</UiAlertDialogTrigger>
  <UiAlertDialogPortal>
    <UiAlertDialogBackdrop />
    <UiAlertDialogViewport>
      <UiAlertDialogPopup>
        <UiAlertDialogTitle>Title</UiAlertDialogTitle>
        <UiAlertDialogDescription>Body</UiAlertDialogDescription>
        <UiAlertDialogClose>Cancel</UiAlertDialogClose>
      </UiAlertDialogPopup>
    </UiAlertDialogViewport>
  </UiAlertDialogPortal>
</UiAlertDialogRoot>

Examples

Open from a menu

Control the dialog and open it from a menu item click. The menu closes first; the alert stays until Cancel, the filled action, or Escape.

Close confirmation

Nest the alert inside a controlled Dialog. If the parent would close while the textarea has text, keep the dialog open and show the alert instead. The parent popup sets data-nested-dialog-open and --nested-dialogs. Nested backdrops are not rendered, so the dialog stays visible behind the alert.

Detached triggers

For a one-off, put UiAlertDialogTrigger inside Root. When that is impractical, create a handle with createAlertDialogHandle() and pass it to both the trigger and the root.

<script setup>
import { createAlertDialogHandle } from '~/lib/ui/alert-dialog'

const demoAlertDialog = createAlertDialogHandle()
</script>

<template>
  <UiAlertDialogTrigger :handle="demoAlertDialog">Open</UiAlertDialogTrigger>
  <UiAlertDialogRoot :handle="demoAlertDialog">
    …
  </UiAlertDialogRoot>
</template>

Multiple triggers

One alert can be opened by several triggers. Place multiple triggers inside Root, or give detached triggers the same handle. Pass payload on the trigger and read it from Root’s default slot.

<UiAlertDialogRoot v-slot="{ payload }">
  <UiAlertDialogTrigger :payload="{ message: 'left photo' }">Left photo</UiAlertDialogTrigger>
  <UiAlertDialogTrigger :payload="{ message: 'right photo' }">Right photo</UiAlertDialogTrigger>
  …
</UiAlertDialogRoot>

Controlled mode with multiple triggers

Use v-model:open (or open / @update:open) with a handle. Set id on each trigger and triggerId on Root so the matching trigger receives data-popup-open. Opening from a plain button calls handle.show(payload, id).

API reference

Root

Groups all parts of the alert dialog. Renders nothing of its own. Default slot receives { payload, triggerId }.

PropTypeDefaultDescription
defaultOpenbooleanfalseUncontrolled initial open. Use open / v-model:open for a controlled dialog.
openboolean—Controlled open state (v-model:open).
handleBaseAlertDialogHandle—Links detached triggers. Create with createAlertDialogHandle().
triggerIdstring | null—Active trigger id in controlled mode.

Trigger

Button that opens the alert dialog. Renders a <button>.

PropTypeDefaultDescription
disabledboolean—Ignore presses. Sets data-disabled.
handleBaseAlertDialogHandle—Detached handle. Required when the trigger is outside Root.
payloadunknown—Value exposed on Root’s slot when this trigger opens the dialog.
idstringgeneratedTrigger id. Used with Root triggerId.
AttributeDescription
data-popup-openPresent when this trigger’s dialog is open.
data-disabledPresent when the trigger is disabled.

Portal

Teleports to body while the overlay is present (useCssPresence).

AttributeDescription
data-openPresent when the dialog is open.
data-closedPresent while present but not open (exit transition).
data-nestedPresent when nested in another overlay.
data-starting-stylePresent while animating in.
data-ending-stylePresent while animating out.

Backdrop

Covers the viewport. Does not close the dialog. Nested dialogs skip rendering the child backdrop unless forceRender is set. Uses position: absolute inside the fixed portal.

PropTypeDefaultDescription
forceRenderboolean—Render even when nested.
AttributeDescription
data-openPresent when the dialog is open.
data-closedPresent when the dialog is closed.
data-starting-stylePresent while animating in.
data-ending-stylePresent while animating out.

Viewport

Centers the popup.

AttributeDescription
data-openPresent when the dialog is open.
data-closedPresent when the dialog is closed.
data-nestedPresent when nested in another overlay.
data-nested-dialog-openPresent when a nested dialog is open.
data-starting-stylePresent while animating in.
data-ending-stylePresent while animating out.

role="alertdialog". Escape closes. Focus stays inside until close. Enter and leave use data-starting-style / data-ending-style (scale 0.98, 100ms). No animation library. No drop-shadow.

AttributeDescription
data-openPresent when the dialog is open.
data-closedPresent when the dialog is closed.
data-nestedPresent when nested in another overlay.
data-nested-dialog-openPresent when a nested dialog is open.
data-starting-stylePresent while animating in.
data-ending-stylePresent while animating out.
VariableDescription
--nested-dialogsHow many dialogs are nested inside this one.

Title

Heading that labels the dialog. Renders an <h2>.

Description

Supporting text. Renders a <p>.

Close

Button that sets open to false. Add class="cta" for the filled action.

PropTypeDefaultDescription
disabledboolean—Ignore presses. Sets data-disabled.
AttributeDescription
data-disabledPresent when the button is disabled.

createAlertDialogHandle

Creates a handle that connects UiAlertDialogRoot with detached UiAlertDialogTrigger components.

const handle = createAlertDialogHandle<{ message: string }>()
handle.show({ message: 'Delete project?' }, 'alert-trigger-2')
handle.hide()
FieldDescription
openRef of whether the dialog is open.
payloadRef of the active trigger payload.
triggerIdRef of the active trigger id.
show(payload?, triggerId?)Open the dialog.
hide()Close the dialog.