dialog
Delete this project?
Deleting it removes every theme and adapter built from it. Members keep their accounts.
Your session is about to expire
You will be signed out in two minutes unless you carry on.
A Dialog is a panel that takes the page over until it is answered. It is the first
component here whose default slot is the trigger rather than the content: the one
element a caller has to own is the button that opens the thing, because it has to be a
real themed Button carrying the dialog’s own props. Everything inside the panel is
addressed by name, because the recipe positions all of it.
<Dialog
title="Delete this project?"
description="This cannot be undone."
body={<p>Deleting it removes every theme and adapter built from it.</p>}
footer={<Button color="error">Delete</Button>}
>
<Button variant="outline" color="neutral">
Open a dialog
</Button>
</Dialog>
<Dialog title="Delete this project?" description="This cannot be undone.">
<Button variant="outline" color="neutral">Open a dialog</Button>
<template #body>
<p>Deleting it removes every theme and adapter built from it.</p>
</template>
<template #footer>
<Button color="error">Delete</Button>
</template>
</Dialog>
That is the rule to expect from every component after this one. Content the recipe positions is named. The default slot is spent on the caller’s own element when there is one to hand over.
The parts
title and description are strings, and they build the header between them. body and
footer take markup. Passing header replaces the whole header, close button included,
for the rare panel that wants its own.
Leaving a part out leaves its element out too: a dialog with no footer has no footer
row and no border above one.
Sizes
Four, from sm to xl, and they scale the panel’s width along with its padding and its
type. fullscreen ignores the width entirely and fills the viewport.
The panel never scrolls itself. It caps its own height and hides its overflow, and the body takes what is left and scrolls, so a long form moves under a header and a footer that stay where they are.
Getting out
dismissible is the single switch over both of Ark’s: Escape and a click outside. Turn
it off and the close button and whatever is in the footer become the only ways out, which
is what a dialog guarding unsaved work wants.
<Dialog role="alertdialog" dismissible={false} title="Your session is about to expire">
<Button>Keep working</Button>
</Dialog>
<Dialog role="alertdialog" :dismissible="false" title="Your session is about to expire">
<Button>Keep working</Button>
</Dialog>
close hides the close button on its own, and closeIcon replaces it. role set to
alertdialog tells a screen reader the dialog interrupts rather than merely appears.
Open and closed
Open is not a recipe variant. Ark writes data-state on the panel and the overlay, and
the recipe styles itself off that, so one resolved class string covers both states.
React takes open with onOpenChange, or defaultOpen to leave the state alone. Vue
takes v-model:open, with defaultOpen as the uncontrolled counterpart. Give neither a
trigger and the dialog is driven entirely from outside.
const [open, setOpen] = useState(false);
<Dialog open={open} onOpenChange={(details) => setOpen(details.open)} title="Controlled" />;
<script setup lang="ts">
const open = ref(false);
</script>
<template>
<Dialog v-model:open="open" title="Controlled" />
</template>
The motion
The panel scales and fades in over an overlay that fades with it, using keyframes shared
with every component that appears over the page. transition turns both off, for an
application that already answers prefers-reduced-motion itself. overlay drops the
backdrop without touching the panel.
Enter and exit name different keyframes on purpose. Ark decides how long to hold the panel mounted by reading the animation name off the closed state, and a name matching the open one makes it unmount at once, skipping the exit with nothing to show for it.
The root slot
base is the panel, not a root element, because Ark’s dialog root renders nothing at all.
A class at the call site lands there, which is what “style this dialog” means.
One consequence worth knowing before writing a test: data-slot="base" is not unique on
the page, since every component’s root slot carries it and a Button inside the panel
has one too. Select the panel with Ark’s own [data-scope="dialog"][data-part="content"].
Staying mounted
unmountOnExit takes the panel out of the DOM once it has finished closing, and
lazyMount keeps it out until it is opened the first time. Both are off by default,
because a panel that stays mounted is the one that keeps its scroll position and its form
state. portal set to false leaves the panel where it was written, for the rare case
that wants it inside its own stacking context.
Slots
The same word names the recipe slot, the data-slot attribute and the key in ui.
- base
- overlay
- positioner
- header
- wrapper
- title
- description
- body
- footer
- closeTrigger
Variants
Read off the recipe, so these are the values that actually resolve. A Theme layer can change which one is the default; it cannot add a value.
- size
- smmdlgxl
- transition
- truefalse
- fullscreen
- truefalse
Props
Taken by both adapters, read out of the component module. The same names and the same types work in React and Vue.
- ui
DialogUI
Per-slot class overrides.
- size
"sm" | "md" | "lg" | "xl"
Defaults to
"md"- transition
DialogVariants["transition"]
Defaults to
"true"- fullscreen
DialogVariants["fullscreen"]
Defaults to
"false"- title
string
Header title. Also names the panel for a screen reader.
- description
string
Header text under the title.
- overlay
boolean
Draw the overlay behind the panel.
Defaults to
true- dismissible
boolean
Close on Escape and on a click outside the panel. Turning it off leaves the close button and the caller's own controls as the only ways out, which is what a dialog asking to confirm something destructive wants.
Defaults to
true- close
boolean
Show the close button in the header.
Defaults to
true- closeIcon
Component
Replaces the close button's icon.
- modal
boolean
Whether the rest of the page is inert while the dialog is open.
Defaults to
true- role
"dialog" | "alertdialog"
`"alertdialog"` tells a screen reader the dialog interrupts.
Defaults to
"dialog"- portal
boolean
Move the panel to the end of the document.
Defaults to
true- lazyMount
boolean
Keep the panel out of the DOM until it is opened for the first time.
- unmountOnExit
boolean
Remove the panel from the DOM once it has finished closing.
Vue only
Binds v-model:open.
- class
unknown
- defaultOpen
boolean
- ids
{ trigger?: string; positioner?: string; backdrop?: string; content?: string; closeTrigger?: string; title?: string; description?: string; }
- #default
- The element that opens the dialog. It becomes the trigger and carries Ark's props.
- #header
- Replaces the whole header, including the title, description and close button.
- #title
- Overrides the `title` prop.
- #description
- Overrides the `description` prop.
- #body
- The panel's main content.
- #footer
- The row along the bottom of the panel, usually buttons.