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-react
import { Dialog } from "@wizeworks/silicaui-react";

Props

DialogTriggerProps
PropTypeRequiredDescription
childrenReact.ReactElementyes—
nativeButtonbooleannoWhether children is a real <button>.
DialogContentProps
PropTypeRequiredDescription
childrenReact.ReactNodeno—
backdropClassNamestringnoClass for the backdrop layer.
DialogHeaderProps
PropTypeRequiredDescription
stickybooleannoPin this bar in place while the rest of the content scrolls.
DialogFooterProps
PropTypeRequiredDescription
stickybooleannoPin 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=&quot;glass&quot;</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-behaviors
import { 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.