API
Dialog
Modal dialogs on the native `<dialog>`, opened by invoker commands.
Installation
pnpm dlx zazz-ui add dialognpx zazz-ui add dialogyarn dlx zazz-ui add dialogbunx zazz-ui add dialogRun zazz-ui init first if the project has no zazz/ folder yet.
Examples
Default
<button data-ui="button" type="button" command="show-modal" commandfor="dialog-example-1">
Dialog
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<line
x1="64"
y1="192"
x2="192"
y2="64"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
<polyline
points="88 64 192 64 192 168"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
</svg>
</button>
<dialog id="dialog-example-1" data-ui="dialog" closedby="any">
<div data-dialog-slot="content">
<header data-dialog-slot="header">
<h2
style="
--font-size: var(--font-size-lg);
--font-weight: strong;
--font-family: var(--font-family-heading);
"
>
Dialog title
</h2>
<p style="--text: var(--color-muted-foreground)">
This dialog was opened using an
<a
href="https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API"
target="_blank"
style="--text: var(--color-primary)"
>invoker command</a
>.
</p>
</header>
<div data-dialog-slot="body">
<p>Set the size of the dialog with the <code>data-dialog-size</code> attribute.</p>
</div>
<footer data-dialog-slot="footer">
<button data-ui="button" type="button" commandfor="dialog-example-1" command="close">
Cancel
</button>
<button
data-ui="button"
data-button-variant="primary"
commandfor="dialog-example-1"
command="close"
>
Accept
</button>
</footer>
</div>
<button
data-ui="button"
data-dialog-slot="close"
data-button-size="icon"
data-button-variant="ghost"
type="button"
commandfor="dialog-example-1"
command="close"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<line
x1="200"
y1="56"
x2="56"
y2="200"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
<line
x1="200"
y1="200"
x2="56"
y2="56"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
</svg>
</button>
</dialog>/**
* dialog.css — Dialog ([data-ui~="dialog"] on native <dialog>; slots
* data-dialog-slot="content | header | body | footer | close")
*
* @layer variables, reset, components
* @requires _layers.css, _variables.css, _properties.css (--_*-resolved)
* @uses <dialog>, ::backdrop — native modal + backdrop
* @uses @starting-style + allow-discrete — open/close opacity, transform, overlay
* @uses anchor-name, container-type — layout hooks for sized variants
* @tokens --ui-dialog-* (@layer variables; --ui-dialog-h defaults to a keyword, so it is read
* raw: a --h length, or a tier carrying a length, applies; a scale
* number applies only as a base utility)
* @presets data-dialog-size="article | container | screen"
* @consumedby alert-dialog.css, mobile-menu.css
*/
@layer variables {
:root {
--ui-dialog-backdrop-color: var(--color-shade-900);
--ui-dialog-transition-duration: var(--default-transition-duration);
--ui-dialog-transition-timing-function: var(--default-transition-timing-function);
--ui-dialog-backdrop-transition-duration: calc(var(--ui-dialog-transition-duration) - 100ms);
--ui-dialog-backdrop-transition-timing-function: ease;
--ui-dialog-backdrop-filter: none;
--ui-dialog-shadow: var(--shadow-lg);
--ui-dialog-border: 1px solid var(--color-border);
--ui-dialog-w: min(var(--layout-md), 100% - var(--gutters) * 2);
/* Reveal transitions/animations */
--ui-dialog-opacity--start: 0;
--ui-dialog-opacity--end: 1;
--ui-dialog-scale--start: 0.98;
--ui-dialog-scale--end: 1;
--ui-dialog-translate--start: 0, 0, 0;
--ui-dialog-translate--end: 0, 0, 0;
--ui-dialog-transition-delay--start: 0ms;
--ui-dialog-transition-delay--end: 150ms;
}
}
/* Second variables block: overrides --ui-dialog-w above. */
@layer variables {
:root {
--ui-dialog-w: min(50ch, 100% - var(--space-md) * 2);
--ui-dialog-h: fit-content;
--ui-dialog-bg: var(--color-card);
--ui-dialog-text: var(--color-card-foreground);
--ui-dialog-rounded: var(--radius-lg);
}
}
@layer reset {
/* ===========================================================================
DIALOG — native <dialog> open/close animation, sizing, backdrop fade
- In the reset layer so the dialog component styling below wins.
=========================================================================== */
/* Spring-eased open/close */
:where(dialog) {
margin: auto;
max-inline-size: calc(100% - var(--space-md));
max-block-size: calc(100% - var(--space-md));
border: var(--ui-dialog-border);
contain: layout style;
opacity: var(--ui-dialog-opacity--start);
box-shadow: var(--ui-dialog-shadow);
overscroll-behavior: contain;
overflow: auto;
transform: translate3d(var(--ui-dialog-translate--start)) scale(var(--ui-dialog-scale--start));
transition:
opacity var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
transform var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
padding var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
block-size var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
inline-size var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
overlay var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function)
allow-discrete,
display var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function)
allow-discrete;
transition-delay: var(--ui-dialog-transition-delay--start);
}
:where(dialog[open]) {
transform: translate3d(var(--ui-dialog-translate--end)) scale(var(--ui-dialog-scale--end));
opacity: var(--ui-dialog-opacity--end);
transition-delay: var(--ui-dialog-transition-delay--end);
@starting-style {
transition-delay: var(--ui-dialog-transition-delay--start);
transform: translate3d(var(--ui-dialog-translate--start)) scale(var(--ui-dialog-scale--start));
opacity: var(--ui-dialog-opacity--start);
}
}
/* Backdrop fades via a discrete transition */
:where(dialog)::backdrop {
position: fixed;
inline-size: 100%;
block-size: 100%;
inset: 0;
z-index: 1000;
opacity: 0;
overscroll-behavior: contain;
overflow: auto;
background-color: var(--ui-dialog-backdrop-color);
backdrop-filter: var(--ui-dialog-backdrop-filter);
transition: opacity var(--ui-dialog-backdrop-transition-duration)
var(--ui-dialog-backdrop-transition-timing-function);
}
:where(dialog[open])::backdrop {
opacity: 1;
@starting-style {
opacity: 0;
}
}
}
/* Size privates: written by data-dialog-size, non-inheriting. */
@property --_dialog-w {
syntax: "*";
inherits: false;
}
@property --_dialog-h {
syntax: "*";
inherits: false;
}
@property --_dialog-rounded {
syntax: "*";
inherits: false;
}
@property --_dialog-shadow {
syntax: "*";
inherits: false;
}
@layer ui.components {
:where([data-ui~="dialog"]) {
/* Set the hook on the element (not :root) so a later consumer (alert-dialog)
or a subtree hook still wins; --_dialog-shadow is written only by the
screen preset. */
--ui-dialog-shadow: var(--shadow-md);
--_w-len: var(--_w-resolved, var(--_dialog-w, var(--ui-dialog-w)));
--_w-num: var(--_w-resolved, var(--_dialog-w, var(--ui-dialog-w)));
inline-size: calc(var(--_w-len) + var(--_w-num) * var(--spacing));
block-size: var(--_h-resolved, var(--_dialog-h, var(--ui-dialog-h)));
background-color: var(--_bg-resolved, var(--ui-dialog-bg));
color: var(--_text-resolved, var(--ui-dialog-text));
box-shadow: var(--_shadow-resolved, var(--_dialog-shadow, var(--ui-dialog-shadow)));
border-radius: var(--_rounded-resolved, var(--_dialog-rounded, var(--ui-dialog-rounded)));
anchor-name: --ui-dialog-root;
container-type: inline-size;
container-name: dialog;
font-size: var(--font-size-md);
isolation: isolate;
}
:where([data-ui~="dialog"][open]) {
grid-template-rows: minmax(0, 1fr);
min-block-size: 0;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="close"] {
position: absolute;
inset-block-start: var(--space-xs);
inset-inline-end: var(--space-xs);
z-index: 50;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="close"] > svg {
inline-size: calc(var(--spacing) * 4);
block-size: calc(var(--spacing) * 4);
pointer-events: none;
justify-content: center;
align-items: center;
display: flex;
padding: 0;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="content"] {
display: grid;
align-self: center;
grid-template-rows: auto minmax(0, 1fr) auto;
position: relative;
min-block-size: 0;
overflow: clip;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="header"] {
display: flex;
flex-direction: column;
padding-inline: var(--space-sm);
padding-block-start: var(--space-sm);
}
:where([data-ui~="dialog"]) [data-dialog-slot~="header"]::after {
content: "";
display: block;
position: absolute;
inset-block-start: 100%;
inline-size: 100%;
block-size: var(--space-md);
background: linear-gradient(to top, transparent, var(--ui-dialog-bg));
}
:where([data-ui~="dialog"]) [data-dialog-slot~="body"] {
display: grid;
padding-inline: var(--space-sm);
padding-block: var(--space-md);
min-block-size: 0;
min-block-size: var(--space-xl);
overflow-x: clip;
overflow-y: auto;
scrollbar-gutter: stable both-edges;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="footer"] {
block-size: fit-content;
position: sticky;
inset-block-end: 0;
display: flex;
justify-content: flex-end;
align-self: end;
flex-shrink: 0;
gap: var(--space-xs);
padding: var(--space-sm);
border-block-start: 1px solid var(--color-border);
background-color: var(--ui-dialog-bg);
}
:where([data-ui~="dialog"][data-dialog-size="article"]) {
--_dialog-w: var(--article-md);
}
:where([data-ui~="dialog"][data-dialog-size="container"]) {
--_dialog-w: var(--layout-md);
}
:where([data-ui~="dialog"][data-dialog-size="screen"]) {
--_dialog-w: 100vi;
--_dialog-h: 100svh;
--_dialog-rounded: 0;
--_dialog-shadow: none;
max-inline-size: 100vi;
max-block-size: 100svh;
}
}With form
<button data-ui="button" type="button" command="show-modal" commandfor="form-dialog-1">
New project
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<line
x1="40"
y1="128"
x2="216"
y2="128"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
<line
x1="128"
y1="40"
x2="128"
y2="216"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
</svg>
</button>
<dialog id="form-dialog-1" data-ui="dialog" closedby="any" data-dialog-size="article">
<div data-dialog-slot="content">
<header data-dialog-slot="header">
<h2
style="
--font-size: var(--font-size-lg);
--font-weight: strong;
--font-family: var(--font-family-heading);
"
>
New project
</h2>
<p style="--text: var(--color-muted-foreground)">
Add a project to your workspace. You can change these settings later.
</p>
</header>
<form
id="f-new-project-1"
style="
--display: flex-col;
--gap: 6;
"
data-dialog-slot="body"
method="dialog"
>
<fieldset data-ui="field-group">
<legend data-ui="sr-only">Contact</legend>
<div
style="
--display: grid;
--grid-cols: 2;
--gap: 4;
"
>
<div data-ui="field">
<label data-field-slot="label" for="fd-name">Name</label>
<input
data-ui="input"
id="fd-name"
name="name"
type="text"
inputmode="text"
placeholder="Ada Lovelace"
required
/>
</div>
<div data-ui="field">
<label data-field-slot="label" for="fd-email">Email</label>
<input
data-ui="input"
id="fd-email"
name="email"
type="email"
inputmode="email"
required
placeholder="you@example.com"
autocomplete="email"
aria-describedby="fd-email-hint"
/>
<div data-field-slot="description">
<span data-field-slot="hint" id="fd-email-hint"
>We'll only use this to send receipts.</span
>
<span data-field-slot="error" role="alert">Please enter a valid email address.</span>
</div>
</div>
<label data-ui="field" style="--col-span: 2">
<span data-field-slot="label">Repository</span>
<div data-ui="input-group">
<input
data-ui="input"
type="search"
name="repo"
id="fd-repo"
inputmode="search"
placeholder="Search repositories…"
/>
<span data-input-group-slot="addon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<circle
cx="112"
cy="112"
r="80"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
<line
x1="168.57"
y1="168.57"
x2="224"
y2="224"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
</svg>
</span>
</div>
</label>
</div>
</fieldset>
<fieldset data-ui="field-group">
<div data-ui="field">
<label data-field-slot="label" for="fd-message">Description</label>
<textarea
data-ui="textarea"
id="fd-message"
name="description"
inputmode="text"
placeholder="What is this project for?"
></textarea>
</div>
</fieldset>
<fieldset data-ui="radio-group">
<legend>Billing</legend>
<label data-ui="field" data-field-orientation="horizontal">
<input data-ui="radio" type="radio" name="billing" value="monthly" />
<span data-field-slot="label">Monthly billing</span>
</label>
<label data-ui="field" data-field-orientation="horizontal">
<input data-ui="radio" type="radio" name="billing" value="annual" checked />
<span data-field-slot="label">Annual billing</span>
</label>
</fieldset>
<label data-ui="field" data-field-orientation="horizontal">
<input type="checkbox" name="terms" required />
<span data-field-slot="label">Accept terms & conditions</span>
</label>
</form>
<footer data-dialog-slot="footer">
<button
data-ui="button"
type="button"
value="cancel"
commandfor="form-dialog-1"
command="close"
>
Cancel
</button>
<button
data-ui="button"
data-button-variant="primary"
type="submit"
form="f-new-project-1"
value="save"
>
Create project
</button>
</footer>
<button
data-ui="button"
data-dialog-slot="close"
data-button-size="icon"
data-button-variant="ghost"
type="button"
commandfor="form-dialog-1"
command="close"
aria-label="Close dialog"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<line
x1="200"
y1="56"
x2="56"
y2="200"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
<line
x1="200"
y1="200"
x2="56"
y2="56"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="16"
/>
</svg>
</button>
</div>
</dialog>/**
* dialog.css — Dialog ([data-ui~="dialog"] on native <dialog>; slots
* data-dialog-slot="content | header | body | footer | close")
*
* @layer variables, reset, components
* @requires _layers.css, _variables.css, _properties.css (--_*-resolved)
* @uses <dialog>, ::backdrop — native modal + backdrop
* @uses @starting-style + allow-discrete — open/close opacity, transform, overlay
* @uses anchor-name, container-type — layout hooks for sized variants
* @tokens --ui-dialog-* (@layer variables; --ui-dialog-h defaults to a keyword, so it is read
* raw: a --h length, or a tier carrying a length, applies; a scale
* number applies only as a base utility)
* @presets data-dialog-size="article | container | screen"
* @consumedby alert-dialog.css, mobile-menu.css
*/
@layer variables {
:root {
--ui-dialog-backdrop-color: var(--color-shade-900);
--ui-dialog-transition-duration: var(--default-transition-duration);
--ui-dialog-transition-timing-function: var(--default-transition-timing-function);
--ui-dialog-backdrop-transition-duration: calc(var(--ui-dialog-transition-duration) - 100ms);
--ui-dialog-backdrop-transition-timing-function: ease;
--ui-dialog-backdrop-filter: none;
--ui-dialog-shadow: var(--shadow-lg);
--ui-dialog-border: 1px solid var(--color-border);
--ui-dialog-w: min(var(--layout-md), 100% - var(--gutters) * 2);
/* Reveal transitions/animations */
--ui-dialog-opacity--start: 0;
--ui-dialog-opacity--end: 1;
--ui-dialog-scale--start: 0.98;
--ui-dialog-scale--end: 1;
--ui-dialog-translate--start: 0, 0, 0;
--ui-dialog-translate--end: 0, 0, 0;
--ui-dialog-transition-delay--start: 0ms;
--ui-dialog-transition-delay--end: 150ms;
}
}
/* Second variables block: overrides --ui-dialog-w above. */
@layer variables {
:root {
--ui-dialog-w: min(50ch, 100% - var(--space-md) * 2);
--ui-dialog-h: fit-content;
--ui-dialog-bg: var(--color-card);
--ui-dialog-text: var(--color-card-foreground);
--ui-dialog-rounded: var(--radius-lg);
}
}
@layer reset {
/* ===========================================================================
DIALOG — native <dialog> open/close animation, sizing, backdrop fade
- In the reset layer so the dialog component styling below wins.
=========================================================================== */
/* Spring-eased open/close */
:where(dialog) {
margin: auto;
max-inline-size: calc(100% - var(--space-md));
max-block-size: calc(100% - var(--space-md));
border: var(--ui-dialog-border);
contain: layout style;
opacity: var(--ui-dialog-opacity--start);
box-shadow: var(--ui-dialog-shadow);
overscroll-behavior: contain;
overflow: auto;
transform: translate3d(var(--ui-dialog-translate--start)) scale(var(--ui-dialog-scale--start));
transition:
opacity var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
transform var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
padding var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
block-size var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
inline-size var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function),
overlay var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function)
allow-discrete,
display var(--ui-dialog-transition-duration) var(--ui-dialog-transition-timing-function)
allow-discrete;
transition-delay: var(--ui-dialog-transition-delay--start);
}
:where(dialog[open]) {
transform: translate3d(var(--ui-dialog-translate--end)) scale(var(--ui-dialog-scale--end));
opacity: var(--ui-dialog-opacity--end);
transition-delay: var(--ui-dialog-transition-delay--end);
@starting-style {
transition-delay: var(--ui-dialog-transition-delay--start);
transform: translate3d(var(--ui-dialog-translate--start)) scale(var(--ui-dialog-scale--start));
opacity: var(--ui-dialog-opacity--start);
}
}
/* Backdrop fades via a discrete transition */
:where(dialog)::backdrop {
position: fixed;
inline-size: 100%;
block-size: 100%;
inset: 0;
z-index: 1000;
opacity: 0;
overscroll-behavior: contain;
overflow: auto;
background-color: var(--ui-dialog-backdrop-color);
backdrop-filter: var(--ui-dialog-backdrop-filter);
transition: opacity var(--ui-dialog-backdrop-transition-duration)
var(--ui-dialog-backdrop-transition-timing-function);
}
:where(dialog[open])::backdrop {
opacity: 1;
@starting-style {
opacity: 0;
}
}
}
/* Size privates: written by data-dialog-size, non-inheriting. */
@property --_dialog-w {
syntax: "*";
inherits: false;
}
@property --_dialog-h {
syntax: "*";
inherits: false;
}
@property --_dialog-rounded {
syntax: "*";
inherits: false;
}
@property --_dialog-shadow {
syntax: "*";
inherits: false;
}
@layer ui.components {
:where([data-ui~="dialog"]) {
/* Set the hook on the element (not :root) so a later consumer (alert-dialog)
or a subtree hook still wins; --_dialog-shadow is written only by the
screen preset. */
--ui-dialog-shadow: var(--shadow-md);
--_w-len: var(--_w-resolved, var(--_dialog-w, var(--ui-dialog-w)));
--_w-num: var(--_w-resolved, var(--_dialog-w, var(--ui-dialog-w)));
inline-size: calc(var(--_w-len) + var(--_w-num) * var(--spacing));
block-size: var(--_h-resolved, var(--_dialog-h, var(--ui-dialog-h)));
background-color: var(--_bg-resolved, var(--ui-dialog-bg));
color: var(--_text-resolved, var(--ui-dialog-text));
box-shadow: var(--_shadow-resolved, var(--_dialog-shadow, var(--ui-dialog-shadow)));
border-radius: var(--_rounded-resolved, var(--_dialog-rounded, var(--ui-dialog-rounded)));
anchor-name: --ui-dialog-root;
container-type: inline-size;
container-name: dialog;
font-size: var(--font-size-md);
isolation: isolate;
}
:where([data-ui~="dialog"][open]) {
grid-template-rows: minmax(0, 1fr);
min-block-size: 0;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="close"] {
position: absolute;
inset-block-start: var(--space-xs);
inset-inline-end: var(--space-xs);
z-index: 50;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="close"] > svg {
inline-size: calc(var(--spacing) * 4);
block-size: calc(var(--spacing) * 4);
pointer-events: none;
justify-content: center;
align-items: center;
display: flex;
padding: 0;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="content"] {
display: grid;
align-self: center;
grid-template-rows: auto minmax(0, 1fr) auto;
position: relative;
min-block-size: 0;
overflow: clip;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="header"] {
display: flex;
flex-direction: column;
padding-inline: var(--space-sm);
padding-block-start: var(--space-sm);
}
:where([data-ui~="dialog"]) [data-dialog-slot~="header"]::after {
content: "";
display: block;
position: absolute;
inset-block-start: 100%;
inline-size: 100%;
block-size: var(--space-md);
background: linear-gradient(to top, transparent, var(--ui-dialog-bg));
}
:where([data-ui~="dialog"]) [data-dialog-slot~="body"] {
display: grid;
padding-inline: var(--space-sm);
padding-block: var(--space-md);
min-block-size: 0;
min-block-size: var(--space-xl);
overflow-x: clip;
overflow-y: auto;
scrollbar-gutter: stable both-edges;
}
:where([data-ui~="dialog"]) [data-dialog-slot~="footer"] {
block-size: fit-content;
position: sticky;
inset-block-end: 0;
display: flex;
justify-content: flex-end;
align-self: end;
flex-shrink: 0;
gap: var(--space-xs);
padding: var(--space-sm);
border-block-start: 1px solid var(--color-border);
background-color: var(--ui-dialog-bg);
}
:where([data-ui~="dialog"][data-dialog-size="article"]) {
--_dialog-w: var(--article-md);
}
:where([data-ui~="dialog"][data-dialog-size="container"]) {
--_dialog-w: var(--layout-md);
}
:where([data-ui~="dialog"][data-dialog-size="screen"]) {
--_dialog-w: 100vi;
--_dialog-h: 100svh;
--_dialog-rounded: 0;
--_dialog-shadow: none;
max-inline-size: 100vi;
max-block-size: 100svh;
}
}Light dismiss
closedby="any" on the <dialog> lets a click on the backdrop close it, next to Esc. Chrome 134 and Firefox 141 do this natively. Safari 26 knows closedby but only its none and closerequest values, so any falls back to closerequest and the backdrop does nothing. The kit's dialog lifecycle script fills that gap: where any does not round-trip through the closedBy property, a press and release on the backdrop of a closedby="any" dialog calls requestClose(), which fires cancel as the native path does. Nothing in the markup changes, and the fallback stays idle where the browser handles it. A dialog that holds a form or a search still deserves a visible close button, for the people who find neither the backdrop nor Esc. closedby="none" (alert dialogs) and the default are left alone in both cases.
Attributes
| Attribute | Kind | Values | Notes |
|---|---|---|---|
data-ui="dialog" | Identity | free value | Identity token in data-ui. |
data-dialog-size | Preset | article container screen | Preset: a named bundle of values. |
data-dialog-slot | Slot | body close content footer header | Slot: a part inside the primitive. |
Hooks
Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.
| Hook | Default | State |
|---|---|---|
--ui-dialog-backdrop-color | var(--color-shade-900) | — |
--ui-dialog-backdrop-filter | none | — |
--ui-dialog-backdrop-transition-duration | calc(var(--ui-dialog-transition-duration) - 100ms) | — |
--ui-dialog-backdrop-transition-timing-function | ease | — |
--ui-dialog-bg | var(--color-card) | — |
--ui-dialog-border | 1px solid var(--color-border) | — |
--ui-dialog-h | fit-content | — |
--ui-dialog-opacity--end | 1 | end |
--ui-dialog-opacity--start | 0 | start |
--ui-dialog-rounded | var(--radius-lg) | — |
--ui-dialog-scale--end | 1 | end |
--ui-dialog-scale--start | 0.98 | start |
--ui-dialog-shadow | var(--ui-alert-dialog-shadow) | — |
--ui-dialog-text | var(--color-card-foreground) | — |
--ui-dialog-transition-delay--end | 0ms | end |
--ui-dialog-transition-delay--start | 0ms | start |
--ui-dialog-transition-duration | var(--default-transition-duration) | — |
--ui-dialog-transition-timing-function | var(--default-transition-timing-function) | — |
--ui-dialog-translate--end | 0, 0, 0 | end |
--ui-dialog-translate--start | 0, 0, 0 | start |
--ui-dialog-w | var(--ui-alert-dialog-w) | — |