Components / Overlays
Dialog
A modal window over the page for a task the person must finish or cancel before going on: confirm a destructive action, fill in a short form. The page behind is inert until it closes. In a narrow container (a phone, an extension popup) it becomes a bottom sheet.
When to useLink to "When to use"
- A decision that blocks the work: "Discard changes?", "Delete 3 files?".
- A short, focused form that belongs to the current page (rename, edit details).
Use something else when:
- the content belongs next to a button and can stay open while working: Popover
- it is a list of commands: Menu
- it is a side panel or a phone-style drawer: Sheet
- it is a message that needs no answer: a Notice or a Toast
UsageLink to "Usage"
import { Button, Dialog, TextField } from "@arshad-shah/cynosure";
const [open, setOpen] = useState(false);
<Button onClick={() => setOpen(true)}>Edit profile</Button>
<Dialog
open={open}
onOpenChange={setOpen}
title="Edit profile"
description="Changes are saved to your account."
actions={
<>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="solid" tone="accent" onClick={save}>Save</Button>
</>
}
>
<TextField label="Name" />
</Dialog>Put Cancel first and the main action last. The content scrolls between the title and the actions, so long forms keep both in view.
ConfirmationsLink to "Confirmations"
With dismissible={false} there is no close button and Escape and the backdrop do nothing: the person must pick one of your actions. Focus starts on the first action, so put the safe one first.
Content and stateLink to "Content and state"
The content is in the DOM only while the dialog is open. It mounts when the dialog opens and unmounts once it has faded out, so every opening starts fresh: fields are back at their defaults, and a closed dialog costs nothing to render.
Set keepMounted to keep the content in the DOM while closed. Use it when what the person typed must survive closing, or when the content is expensive to rebuild. Or keep the state outside the dialog, in the component that owns open.
<Dialog open={open} onOpenChange={setOpen} title="New message" keepMounted>
<TextArea label="Message" />
</Dialog>PropsLink to "Props"
| Prop | What it does | Type | Default |
|---|---|---|---|
titleRequired | Heading of the dialog; also its accessible name. | ReactNode | |
open | Whether the dialog is open, when you control it. | boolean | |
defaultOpen | Open on first render when uncontrolled. | boolean | |
onOpenChange | Called with false when the dialog closes itself: Escape, the backdrop or the close button. | ((open: boolean) => void) | |
description | One or two sentences under the title; also its accessible description. | ReactNode | |
actions | Buttons along the bottom, outside the scrolling content (Cancel first, then the main action). | ReactNode | |
size | Width preset (the panel never grows wider than its container). | Size | |
dismissible | Escape, the backdrop and the close button close it (default). false leaves only your actions. | boolean | |
keepMounted | Keep the content in the DOM while the dialog is closed. By default a closed dialog renders nothing inside it: the content mounts when it opens and unmounts once the closing transition has ended, so every opening starts fresh. Set it for content whose state must survive closing (a half-filled form) or that is expensive to rebuild. Default false. | boolean | false |
labels | Replace the built-in English text: the close button's name. | Partial<DialogLabels> | |
className | Added to the panel, for layout. | string | |
style | Layout only, applied to the panel. Restyle through --cy-dialog-* tokens. | CSSProperties | |
children | The content; it scrolls when it is taller than the dialog. | ReactNode |
Generated from the type definitions in @arshad-shah/cynosure 0.5.0.
Behaviour and accessibilityLink to "Behaviour and accessibility"
| Input | Result |
|---|---|
| Escape | Closes the topmost layer: a Menu, Select or Tooltip inside first, then the dialog |
| Tab, Shift + Tab | Move through the dialog only; the page behind is inert |
| Pointer down on the backdrop | Closes it (only a nested Menu when one is open) |
| Close button | Closes it |
| Android back gesture | Closes it |
- Focus: on open, focus moves to the first control in the content, else the first action, else the dialog itself. On close it returns to where it was, usually the button that opened it.
- Mounting: closed, the
<dialog>element is empty, on the server too. The content is rendered in the same render that opens it and removed when the closing transition ends, or at once when there is none (reduced motion). Reopening before the fade has finished keeps the content. WithkeepMountedit always stays. - Role: a native modal
<dialog>, named bytitle(anh2) and described bydescription. - Narrow containers: below 36rem wide, the dialog docks to the bottom edge at full width.
- Isolation: it never writes to
<html>or<body>. There is no scroll lock; the content usesoverscroll-behavior: containso scrolling it does not move the page. - Toasts: inside a Toaster, toasts raised while the dialog is open show above it (not under its backdrop), and move back to the page when it closes.
- Shadow DOM: works inside shadow roots (extension content scripts).
- Motion: a short fade and scale (a slide as a sheet), removed under reduced motion.
TouchLink to "Touch"
The close button keeps a 44px hit area on coarse pointers. A tap on the backdrop closes the dialog, and the bottom-sheet actions stay above the home indicator.
ThemingLink to "Theming"
Tokens: --cy-dialog-radius, --cy-dialog-padding, --cy-dialog-width-{sm,md,lg} and --cy-dialog-scrim.
createTheme({ components: { dialog: { radius: "10px", widthMd: "520px", scrim: "rgb(0 0 0 / 0.6)" } } });RelatedLink to "Related"
Sheet · Popover · Menu · DialogBase
These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.