Dialog
Introduction
A modal window overlaid on the page that requires user interaction before continuing.
Hydration
Tier 1 — auto-interactive by default. A Dialog has no meaningful static fallback — opening, focus trapping, and ESC handling all require client-side JavaScript — so it hydrates as an island by default. Pass interactive={false} to render a static, inert dialog shell that ships no client JS.
interactive prop | Result |
|---|---|
| omitted | Hydrates as an island (default) |
true | Hydrates as an island |
false | Static — no client JS |
All interactivity decisions in the library route through the shared shouldHydrate() helper in app/components/ui/island-utils.ts.
Accessibility
Complies with the Dialog (Modal) WAI-ARIA design pattern. When hydrated (interactive defaults to true), the dialog ships the following behavior:
- Focus moves into the dialog on open — to
initialFocusEl(), else the first focusable element, else the content itself. - Focus is trapped while open —
Tab/Shift+Tabcycle only within the dialog content. Nested dialogs are handled: only the topmost dialog traps and ownsEscape. Escapecloses the dialog (unlesscloseOnEscape={false}).- Background is inert — everything outside the dialog (including a parent dialog behind a nested one) gets
inert+ is removed from the tab order, while the dialog subtree stays interactive. - Body scroll is locked while at least one dialog is open, and restored when the last one closes.
- Focus returns to the trigger on close (or to
finalFocusEl()if provided). - Accessible name — derived from
title(aria-labelledby) or an explicitaria-label. A client-sideconsole.warnis emitted if neither is present. role="alertdialog"— passrole="alertdialog"for destructive confirmations, and pointinitialFocusElat the cancel/safe action so it receives focus first. WithcloseOnInteractOutside={false}the dialog can only be dismissed via a button orEscape.
Usage
Basic Dialog
Confirm action
Confirm action
import { Dialog, Button } from "../components/ui";
export default function MyPage() {
return (
<Dialog
trigger={<Button>Open Dialog</Button>}
title="Confirm action"
description="Are you sure you want to continue?"
body="This action cannot be undone."
cancel={<Button variant="outline">Cancel</Button>}
confirm={<Button>Confirm</Button>}
/>
);
}
CMS Page Builder
This component is available as a dialog block in the Page Builder (content/pages/*.json). trigger is a nested block list (the CMS always submits an array):
{
"type": "dialog",
"title": "Confirm action",
"description": "Are you sure you want to continue?",
"confirmText": "Confirm",
"cancelText": "Cancel",
"trigger": [{ "type": "button", "text": "Open Dialog" }]
}
Props
Dialog
| Prop | Type | Description |
|---|---|---|
trigger | JSX.Element | Element that opens the dialog when activated. |
| title | `string \ | JSX.Element` | The dialog title. |
| description | `string \ | JSX.Element` | The dialog description. |
| body | `string \ | JSX.Element` | The main body content. |
| footer | `string \ | JSX.Element` | Custom footer content. |
| cancel | JSX.Element | Element rendered as a close (cancel) trigger. |
| confirm | JSX.Element | Element rendered as an action trigger. |
| closable | boolean | Whether to show the close button. Default: true. |
| interactive | boolean | Enable client-side hydration for interactive behavior. |
| class | string | Custom CSS classes for the root element. |
| role | `"dialog" \ | "alertdialog"` | Dialog variant. Use "alertdialog" for destructive confirmations. Default: "dialog". |
| aria-label | string | Accessible name when no title is provided. |
| closeOnEscape | boolean | Close when Escape is pressed. Default: true. |
| closeOnInteractOutside | boolean | Close when the backdrop is clicked. Default: true. |
| initialFocusEl | `() => HTMLElement \ | null` | Element to focus on open. Defaults to the first focusable. |
| finalFocusEl | `() => HTMLElement \ | null` | Element to focus on close. Defaults to the trigger. |
Additional props (e.g. open, defaultOpen, onOpenChange, id) are forwarded to the underlying dialog primitive.