Overlay
Dialog
Accessible modal dialog rendered in a portal with backdrop, focus trap, Escape handling and body scroll lock. Composes with DialogHeader/Body/Footer, DialogTitle/Description and DialogClose.
Import
import { Dialog, DialogHeader, DialogBody, DialogFooter, DialogTitle, DialogDescription, DialogClose } from "@zephora/react";Examples
Basic dialog
Control the open state with useState and `onOpenChange`.
const [open, setOpen] = React.useState(false);
<Button onClick={() => setOpen(true)}>Open dialog</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogClose />
</DialogHeader>
<DialogBody>
<DialogDescription>
Changes are saved automatically while you type.
</DialogDescription>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={() => setOpen(false)}>Save</Button>
</DialogFooter>
</Dialog>Sizes
`size` scales the panel width: sm, md, lg, xl or full.
<Dialog open={open} onOpenChange={setOpen} size="lg">
<DialogHeader>
<DialogTitle>Large dialog</DialogTitle>
<DialogClose />
</DialogHeader>
<DialogBody>Wider panel for dense content.</DialogBody>
</Dialog>Blocking dialog
Disable outside-press and Escape dismissal for flows that need an explicit choice.
<Dialog
open={open}
onOpenChange={setOpen}
closeOnOverlayClick={false}
closeOnEscape={false}
>
<DialogHeader>
<DialogTitle>Accept terms</DialogTitle>
</DialogHeader>
<DialogBody>You must choose an option to continue.</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Decline</Button>
<Button onClick={() => setOpen(false)}>Accept</Button>
</DialogFooter>
</Dialog>API
Dialog props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled open state. |
defaultOpen | boolean | false | Initial open state (uncontrolled). |
onOpenChange | (open: boolean) => void | — | Called whenever the dialog requests an open state change. |
onClose | (reason: "escape" | "overlay" | "close-button") => boolean | void | — | Called before the dialog closes itself (Escape, backdrop press or DialogClose). Return false to cancel the close and keep it open. |
size | "sm" | "md" | "lg" | "xl" | "full" | "md" | Panel width scale. |
closeOnOverlayClick | boolean | true | Clicking the backdrop closes the dialog. |
closeOnEscape | boolean | true | Pressing Escape closes the dialog. |
initialFocusRef | RefObject<HTMLElement | null> | — | Element focused when the dialog opens (defaults to the first focusable). |
classNames | Partial<Record<DialogSlot, string>> | — | Per-slot class overrides set once on the root: "root" (fixed positioner) | "backdrop" | "panel" (where className also lands) | "header" | "body" | "footer" | "title" | "description" | "close". Appended after the module classes and also applied in unstyled mode. |
unstyled | boolean | false | Headless mode — skips Zephora styling. |
DialogHeader / DialogBody / DialogFooter props
| Prop | Type | Default | Description |
|---|---|---|---|
unstyled | boolean | false | Headless mode for the layout region. |
…rest | HTMLAttributes<HTMLDivElement> | — | Native div props are forwarded. |
DialogTitle / DialogDescription props
| Prop | Type | Default | Description |
|---|---|---|---|
unstyled | boolean | false | Headless mode. |
id | string | — | Optional id; auto-generated otherwise and wired to the panel via aria-labelledby / aria-describedby. |
DialogClose props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Custom close glyph; defaults to an X icon. |
aria-label | string | "Close" | Accessible name of the close button. |
unstyled | boolean | false | Headless mode. |
Keyboard
| Key | Action |
|---|---|
Escape | Closes the dialog (unless closeOnEscape is false). |
Tab / Shift+Tab | Cycles focus inside the panel — focus is trapped while open. |