Components
Dialog
Modal dialogs built with native dialog elements and Invoker Commands.
Dialogs use native <dialog class="ui-dialog"> elements. Open from buttons with command="show-modal" and commandfor="<id>". Close with command="close". Add closedby="any" for light-dismiss on backdrop click.
Default
Loading components…
<button class="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" class="ui-dialog" closedby="any"> <div data-slot="dialog-content"> <header data-slot="dialog-header"> <h2 class="text-lg font-heading font-strong">Dialog title</h2> <p class="text-muted-foreground"> This dialog was opened using an <a href="https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API" target="_blank" class="text-link" >invoker command</a >. </p> </header> <div data-slot="dialog-body"> <p>Set the size of the dialog with the <code>data-size</code> attribute.</p> </div> <footer data-slot="dialog-footer"> <button class="ui-button" type="button" commandfor="dialog-example-1" command="close"> Cancel </button> <button class="ui-button" data-variant="primary" commandfor="dialog-example-1" command="close" > Accept </button> </footer> </div> <button class="ui-button" data-slot="dialog-close" data-size="icon" data-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 (.ui-dialog on native <dialog>) * * @layer variables, reset, components * @requires layers.css, _variables.css * @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; sizing tokens in a second variables block) * @consumedby alert-dialog.css, mobile-menu.css */@layer variables { :root { --ui-dialog-display: grid; --ui-dialog-backdrop-color: var(--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-blur: blur(5px); --ui-dialog-shadow: var(--shadow-lg); --ui-dialog-border: 1px solid var(--border); --ui-dialog-width: min(var(--breakpoint-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; }}/* Sizing tokens — second variables block (overrides --ui-dialog-width above). */@layer variables { :root { --ui-dialog-width: min(50ch, 100% - var(--gap-md) * 2); --ui-dialog-height: fit-content; --ui-dialog-background: var(--card); --ui-dialog-foreground: var(--card-foreground); --ui-dialog-radius: var(--radius-lg); }}@layer reset { /* =========================================================================== DIALOG — native <dialog> open/close animation, sizing, backdrop fade - In the reset layer so the .ui-dialog component styling below wins. =========================================================================== */ /* Prevent body scroll when a dialog inside the preview is open */ /* Dialog animations with spring easing */ :where(dialog) { margin: auto; max-inline-size: calc(100% - var(--gap-md)); max-block-size: calc(100% - var(--gap-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); } } /* Dialog backdrop with smooth fade transition */ :where(dialog)::backdrop { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 1000; opacity: 0; overscroll-behavior: contain; overflow: auto; background-color: var(--ui-dialog-backdrop-color); backdrop-filter: var(--ui-dialog-backdrop-blur); 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; } }}@layer zazz.components { .ui-dialog { /* Overrides reset-layer --ui-dialog-shadow (reset > variables) */ --ui-dialog-shadow: var(--shadow-md); inline-size: var(--ui-dialog-width); block-size: var(--ui-dialog-height); background-color: var(--ui-dialog-background); color: var(--ui-dialog-foreground); box-shadow: var(--ui-dialog-shadow); border-radius: var(--ui-dialog-radius); anchor-name: --ui-dialog-root; container-type: inline-size; container-name: dialog; font-size: var(--font-size-md); isolation: isolate; } .ui-dialog[open] { grid-template-rows: minmax(0, 1fr); min-block-size: 0; } [data-slot~="dialog-close"] { position: absolute; top: var(--gap-xs); right: var(--gap-xs); z-index: 50; } [data-slot~="dialog-close"] > svg { inline-size: var(--step-4); block-size: var(--step-4); pointer-events: none; justify-content: center; align-items: center; display: flex; padding: 0; } [data-slot~="dialog-content"] { display: grid; align-self: center; grid-template-rows: auto minmax(0, 1fr) auto; position: relative; min-block-size: 0; overflow: clip; } [data-slot~="dialog-header"] { display: flex; flex-direction: column; padding-inline: var(--gap-sm); padding-block-start: var(--gap-sm); } /* Sticky top edge */ [data-slot~="dialog-header"]::after { content: ""; display: block; position: absolute; top: 100%; inline-size: 100%; block-size: var(--gap-md); background: linear-gradient(to top, transparent, var(--ui-dialog-background)); } [data-slot~="dialog-body"] { display: grid; padding-inline: var(--gap-sm); padding-block: var(--gap-md); min-block-size: 0; min-block-size: var(--gap-xl); overflow-x: clip; overflow-y: auto; scrollbar-gutter: stable both-edges; } [data-slot~="dialog-footer"] { block-size: fit-content; position: sticky; bottom: 0; display: flex; justify-content: flex-end; align-self: end; flex-shrink: 0; gap: var(--gap-xs); padding: var(--gap-sm); border-top: 1px solid var(--border); background-color: var(--ui-dialog-background); } .ui-dialog[data-size="article"] { --ui-dialog-width: var(--article-md); } .ui-dialog[data-size="container"] { --ui-dialog-width: var(--breakpoint-md); } .ui-dialog[data-size="screen"] { --ui-dialog-width: 100vi; --ui-dialog-height: 100svh; --ui-dialog-radius: 0; --ui-dialog-shadow: none; max-inline-size: 100vi; max-block-size: 100svh; }}With a form
Loading components…
<button class="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" class="ui-dialog" closedby="any" data-size="article"> <div data-slot="dialog-content"> <header data-slot="dialog-header"> <h2 class="text-lg font-heading font-strong">New project</h2> <p class="text-muted-foreground"> Add a project to your workspace. You can change these settings later. </p> </header> <form id="f-new-project-1" class="flex flex-col gap-md" data-slot="dialog-body" method="dialog"> <fieldset class="ui-field-group"> <legend class="sr-only">Contact</legend> <div class="grid grid-cols-2 gap-sm"> <div class="ui-field"> <label data-slot="field-label" for="fd-name">Name</label> <input class="ui-input" id="fd-name" name="name" type="text" inputmode="text" placeholder="Ada Lovelace" required /> </div> <div class="ui-field"> <label data-slot="field-label" for="fd-email">Email</label> <input class="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-slot="field-description"> <span data-slot="field-hint" id="fd-email-hint" >We'll only use this to send receipts.</span > <span data-slot="field-error" role="alert">Please enter a valid email address.</span> </div> </div> <label class="ui-field-group col-span-2"> <span data-slot="field-label">Repository</span> <div class="ui-input-group"> <input class="ui-input" type="search" name="repo" id="fd-repo" inputmode="search" placeholder="Search repositories…" /> <span data-slot="input-group-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 class="ui-field-group"> <div class="ui-field"> <label data-slot="field-label" for="fd-message">Description</label> <textarea class="ui-textarea" id="fd-message" name="description" inputmode="text" placeholder="What is this project for?" ></textarea> </div> </fieldset> <fieldset class="ui-radio-group"> <legend>Billing</legend> <label class="ui-field" data-orientation="horizontal"> <input class="ui-radio" type="radio" name="billing" value="monthly" /> <span data-slot="field-label">Monthly billing</span> </label> <label class="ui-field" data-orientation="horizontal"> <input class="ui-radio" type="radio" name="billing" value="annual" checked /> <span data-slot="field-label">Annual billing</span> </label> </fieldset> <label class="ui-field" data-orientation="horizontal"> <input type="checkbox" name="terms" required /> <span data-slot="field-label">Accept terms & conditions</span> </label> </form> <footer data-slot="dialog-footer"> <button class="ui-button" type="button" value="cancel" commandfor="form-dialog-1" command="close" > Cancel </button> <button class="ui-button" data-variant="primary" type="submit" form="f-new-project-1" value="save" > Create project </button> </footer> <button class="ui-button" data-slot="dialog-close" data-size="icon" data-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 (.ui-dialog on native <dialog>) * * @layer variables, reset, components * @requires layers.css, _variables.css * @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; sizing tokens in a second variables block) * @consumedby alert-dialog.css, mobile-menu.css */@layer variables { :root { --ui-dialog-display: grid; --ui-dialog-backdrop-color: var(--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-blur: blur(5px); --ui-dialog-shadow: var(--shadow-lg); --ui-dialog-border: 1px solid var(--border); --ui-dialog-width: min(var(--breakpoint-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; }}/* Sizing tokens — second variables block (overrides --ui-dialog-width above). */@layer variables { :root { --ui-dialog-width: min(50ch, 100% - var(--gap-md) * 2); --ui-dialog-height: fit-content; --ui-dialog-background: var(--card); --ui-dialog-foreground: var(--card-foreground); --ui-dialog-radius: var(--radius-lg); }}@layer reset { /* =========================================================================== DIALOG — native <dialog> open/close animation, sizing, backdrop fade - In the reset layer so the .ui-dialog component styling below wins. =========================================================================== */ /* Prevent body scroll when a dialog inside the preview is open */ /* Dialog animations with spring easing */ :where(dialog) { margin: auto; max-inline-size: calc(100% - var(--gap-md)); max-block-size: calc(100% - var(--gap-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); } } /* Dialog backdrop with smooth fade transition */ :where(dialog)::backdrop { position: fixed; width: 100%; height: 100%; top: 0; left: 0; z-index: 1000; opacity: 0; overscroll-behavior: contain; overflow: auto; background-color: var(--ui-dialog-backdrop-color); backdrop-filter: var(--ui-dialog-backdrop-blur); 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; } }}@layer zazz.components { .ui-dialog { /* Overrides reset-layer --ui-dialog-shadow (reset > variables) */ --ui-dialog-shadow: var(--shadow-md); inline-size: var(--ui-dialog-width); block-size: var(--ui-dialog-height); background-color: var(--ui-dialog-background); color: var(--ui-dialog-foreground); box-shadow: var(--ui-dialog-shadow); border-radius: var(--ui-dialog-radius); anchor-name: --ui-dialog-root; container-type: inline-size; container-name: dialog; font-size: var(--font-size-md); isolation: isolate; } .ui-dialog[open] { grid-template-rows: minmax(0, 1fr); min-block-size: 0; } [data-slot~="dialog-close"] { position: absolute; top: var(--gap-xs); right: var(--gap-xs); z-index: 50; } [data-slot~="dialog-close"] > svg { inline-size: var(--step-4); block-size: var(--step-4); pointer-events: none; justify-content: center; align-items: center; display: flex; padding: 0; } [data-slot~="dialog-content"] { display: grid; align-self: center; grid-template-rows: auto minmax(0, 1fr) auto; position: relative; min-block-size: 0; overflow: clip; } [data-slot~="dialog-header"] { display: flex; flex-direction: column; padding-inline: var(--gap-sm); padding-block-start: var(--gap-sm); } /* Sticky top edge */ [data-slot~="dialog-header"]::after { content: ""; display: block; position: absolute; top: 100%; inline-size: 100%; block-size: var(--gap-md); background: linear-gradient(to top, transparent, var(--ui-dialog-background)); } [data-slot~="dialog-body"] { display: grid; padding-inline: var(--gap-sm); padding-block: var(--gap-md); min-block-size: 0; min-block-size: var(--gap-xl); overflow-x: clip; overflow-y: auto; scrollbar-gutter: stable both-edges; } [data-slot~="dialog-footer"] { block-size: fit-content; position: sticky; bottom: 0; display: flex; justify-content: flex-end; align-self: end; flex-shrink: 0; gap: var(--gap-xs); padding: var(--gap-sm); border-top: 1px solid var(--border); background-color: var(--ui-dialog-background); } .ui-dialog[data-size="article"] { --ui-dialog-width: var(--article-md); } .ui-dialog[data-size="container"] { --ui-dialog-width: var(--breakpoint-md); } .ui-dialog[data-size="screen"] { --ui-dialog-width: 100vi; --ui-dialog-height: 100svh; --ui-dialog-radius: 0; --ui-dialog-shadow: none; max-inline-size: 100vi; max-block-size: 100svh; }}API
| Attribute | Target | Values |
|---|---|---|
command | Trigger | show-modal, close |
commandfor | Trigger | Target <dialog> id |
data-size | .ui-dialog | article, container, screen |
closedby | <dialog> | any, closerequest, none |
For a dialog that requires an explicit response before proceeding, see Alert dialog.