Skip to content
Cynosurev0.5.0
SearchStorybookGitHubnpmColour mode: System. Switch to Light

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.

Opens modal moves focus in and returns it

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"

tsx
Copy
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.

Content scrolls inside

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.

Not dismissible

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.

Every opening starts fresh

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.

tsx
Copy
<Dialog open={open} onOpenChange={setOpen} title="New message" keepMounted>
  <TextArea label="Message" />
</Dialog>
Keep mounted keeps content and state

PropsLink to "Props"

Every prop of Dialog
PropWhat it doesTypeDefault
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.booleanfalse
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"

Behaviour and accessibility, table 1
InputResult
EscapeCloses the topmost layer: a Menu, Select or Tooltip inside first, then the dialog
Tab, Shift + TabMove through the dialog only; the page behind is inert
Pointer down on the backdropCloses it (only a nested Menu when one is open)
Close buttonCloses it
Android back gestureCloses 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. With keepMounted it always stays.
  • Role: a native modal <dialog>, named by title (an h2) and described by description.
  • 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 uses overscroll-behavior: contain so 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.

ts
Copy
createTheme({ components: { dialog: { radius: "10px", widthMd: "520px", scrim: "rgb(0 0 0 / 0.6)" } } });

Sheet · Popover · Menu · DialogBase

These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.

Getting started
Theming
Icons
Accessibility
Code editors and highlighters
Using Cynosure without React
Build-tool adapters
Components
Accordion
AppShell
AspectRatio
Breadcrumbs
Calendar
Card
Container
Disclosure
Divider
DocumentViewport
Grid
Inline
PageHeader
PanZoomCanvas
ScrollArea
SideNav
SortableList
SplitPane
Stack
StatusBar
Tabs
TitleBar
Toolbar
Tree
VirtualList
ZoomControls
Announcer
Avatar
Badge
CodeBlock
DescriptionList
EmptyState
ErrorBoundary
Field
Heading
IconTile
Image
Kbd
List
LoadingDots
Notice
ProgressBar
Prose
Skeleton
Stat
Steps
Table
Text
Button
Checkbox
Checklist
ColorField
ColorSwatches
Combobox
CompareSlider
CopyButton
DateField
FileDrop
IconButton
KeyValueEditor
Link
NumberField
Pagination
PathField
RadioGroup
SearchField
SegmentedControl
Select
SelectionFrame
SignaturePad
Slider
Switch
TagInput
TextArea
TextField
ThemeToggle
TimeField
ButtonBase
ChoiceBase
DialogBase
FileInputBase
HiddenValue
Icon
InputBase
LinkBase
LiveRegion
TextAreaBase
ChatMessage
Choice
DataGrid
DeviceFrame
HighlightedText
HotkeyField
ListRow
OptionList
OverlayFrame
QRCode
RowActions
Section
SiteFooter
SiteHeader
SkipLink
SortButton
VisuallyHidden
CommandPalette
ContextMenu
Dialog
FloatingToolbar
HoverCard
Menu
Menubar
Popover
ProgressOverlay
Sheet
Toast
Tooltip
Changelog