Dialog
A modal dialog.
Focus trapping, focus restoration, scroll locking and Escape handling come from Radix — behaviour that is genuinely difficult to get right and where hand-rolled versions reliably produce accessibility bugs. Everything visible is ABBA's.
Import
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogBody,
DialogFooter,
DialogClose,
} from "@abbainitiative/ui";Also available from the subpath entry @abbainitiative/ui/dialog.
This component carries its own "use client" directive. You can still render it from a Server Component — you simply cannot pass it a function prop, because functions do not serialise across the boundary.
Examples
Confirmation dialog
Open it, then press Tab repeatedly — focus never leaves. Escape closes and returns focus to the trigger.
<Dialog>
<DialogTrigger asChild>
<Button variant="danger">Delete project</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete this project?</DialogTitle>
<DialogDescription>
This permanently removes the project and all of its data.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button variant="danger">Delete</Button>
</DialogFooter>
</DialogContent>
</Dialog>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | Controlled open state. Omit for uncontrolled use. | |
| onOpenChange | (open: boolean) => void | Called when the open state should change. | |
| defaultOpen | boolean | Initial open state when uncontrolled. | |
| size | "sm" | "md" | "lg" | "xl" | "md" | Maximum width, set on DialogContent. |
| showCloseButton | boolean | true | Renders the built-in close button. Set on DialogContent. |
Accessibility
- Focus moves into the dialog on open and returns to the trigger on close.
- Tab is trapped inside while open.
- Escape closes the dialog.
- Content outside is marked `aria-hidden`, so screen readers cannot wander out.
- Always render a DialogTitle — without it the dialog is announced only as “dialog”.
- Body scroll is locked while open.