Dialog
An accessible modal dialog with focus trapping, scroll locking, and enter/exit animation, driven by Base UI behavior in React and the vanilla behavior runtime in static HTML.
Real use · destructive-action confirmation
Header + Footer · placeable docking bars, sticky footer
Glass · frosted popup over the dimmed backdrop
Using Dialog
Silica ships one design system through three paths. They share a class vocabulary but are not interchangeable — pick the one that matches what you are building.
Plain HTML — CSS classes
The Tailwind v4 plugin. Works in any framework or none — Rails, Django, PHP, Go, a static file. This path ships no JavaScript, so Dialog is styled but inert: its behavior comes from modal. For that behavior with no framework, use the node-tree path below.
/* in your CSS */
@import "tailwindcss";
@plugin "@wizeworks/silicaui";Root class dialog-backdrop, with 7 modifiers:
.dialog-backdrop.dialog-description.dialog-footer.dialog-footer-sticky.dialog-header.dialog-header-sticky.dialog-popup.dialog-title
React
Typed components over those same classes, with behavior and accessibility from Base UI.
npm i @wizeworks/silicaui-reactimport { Dialog } from "@wizeworks/silicaui-react";Props
DialogTriggerProps
| Prop | Type | Required | Description |
|---|---|---|---|
| children | React.ReactElement | yes | — |
| nativeButton | boolean | no | Whether children is a real <button>. |
DialogContentProps
| Prop | Type | Required | Description |
|---|---|---|---|
| children | React.ReactNode | no | — |
| backdropClassName | string | no | Class for the backdrop layer. |
DialogHeaderProps
| Prop | Type | Required | Description |
|---|---|---|---|
| sticky | boolean | no | Pin this bar in place while the rest of the content scrolls. |
DialogFooterProps
| Prop | Type | Required | Description |
|---|---|---|---|
| sticky | boolean | no | Pin this bar in place while the rest of the content scrolls. |
Source of the demo above
The exact file that renders the examples above, so it can never drift from them. It imports a few of this site’s own layout helpers (../lib/…), so read it for the component calls rather than copying it whole.
Show source
import {
Dialog,
DialogTrigger,
DialogClose,
DialogContent,
DialogTitle,
DialogDescription,
DialogHeader,
DialogFooter,
Button,
} from "@wizeworks/silicaui-react";
import { Section, Row } from "../lib/Section";
export function DialogDemo() {
return (
<>
<Section title="Real use · destructive-action confirmation">
<Dialog>
<DialogTrigger>
<Button color="error" variant="outline">
Delete project…
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Delete project?</DialogTitle>
<DialogDescription>
This will permanently delete the project and all its data. This
can't be undone.
</DialogDescription>
<div className="mt-4 flex justify-end gap-2">
<DialogClose>
<Button variant="ghost" color="neutral">
Cancel
</Button>
</DialogClose>
<DialogClose>
<Button color="error">Delete</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
</Section>
<Section title="Header + Footer · placeable docking bars, sticky footer">
<Row>
<Dialog>
<DialogTrigger>
<Button variant="outline">Header/Footer</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Invite teammates</DialogTitle>
<DialogClose>
<Button variant="ghost" shape="circle" size="sm">
✕
</Button>
</DialogClose>
</DialogHeader>
<DialogDescription>
Send an invite by email — they'll get access once they accept.
</DialogDescription>
<DialogFooter>
<DialogClose>
<Button variant="ghost" color="neutral">
Cancel
</Button>
</DialogClose>
<DialogClose>
<Button>Send invite</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger>
<Button variant="outline">Sticky footer (scrolling content)</Button>
</DialogTrigger>
<DialogContent className="max-h-[70dvh]">
<DialogHeader sticky>
<DialogTitle>Terms of service</DialogTitle>
</DialogHeader>
<div>
{Array.from({ length: 12 }, (_, i) => (
<p key={i} className="mb-3">
Section {i + 1} — placeholder legal text to force this
dialog to scroll so the sticky footer below stays pinned.
</p>
))}
</div>
<DialogFooter sticky>
<DialogClose>
<Button variant="ghost" color="neutral">
Decline
</Button>
</DialogClose>
<DialogClose>
<Button>Accept</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</Row>
</Section>
<Section title="Glass · frosted popup over the dimmed backdrop">
<Dialog>
<DialogTrigger>
<Button variant="outline">Glass dialog</Button>
</DialogTrigger>
<DialogContent className="glass">
<DialogTitle>Restart required</DialogTitle>
<DialogDescription>
<code>DialogContent className="glass"</code> frosts the
popup over the existing dimmed backdrop — same class, no new prop.
</DialogDescription>
<div className="mt-4 flex justify-end gap-2">
<DialogClose>
<Button variant="ghost" color="neutral">
Later
</Button>
</DialogClose>
<DialogClose>
<Button>Restart now</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
</Section>
</>
);
}Vanilla — node tree to HTML
modal — Dialog/Drawer/AlertDialog/Lightbox/CommandPalette all share one Root(behavior) > Trigger(part=trigger) + Backdrop(part=backdrop) + Content(part=panel) + Close(part=close) shape; Lightbox layers slide/ prev/next/title(counter) parts and CommandPalette layers search/ item parts on the SAME behavior type rather than getting their own — see modal.ts. Build a node tree, project it with toHtml(), and let the zero-dependency runtime hydrate it. This is the path for generated or user-authored documents — a site builder, CMS output, a static export.
npm i @wizeworks/silicaui-html @wizeworks/silicaui-behaviorsimport { atom, toHtml } from "@wizeworks/silicaui-html";
const node = atom("Dialog", "dialog-backdrop", { /* props */ }, [/* children */]);
const markup = toHtml(node);Styling on this path is the class string in the second argument — the same classes as the Plain HTML section above. There is no color prop here; write "dialog-backdrop dialog-backdrop-primary" rather than passing a colour as a prop.
- Takes children
- yes
- Hydrated by
modal
Load @wizeworks/silicaui-behaviors on the page or this markup renders correctly and does nothing.
The full API for Dialog, on every path, is available through the Silica MCP server.