Dialog
A modal overlay that traps focus and interrupts the user's workflow for important tasks or confirmations.
Uses role="dialog" aria-modal="true"; focus is trapped inside; Esc closes by default; backdrop click closes by default.
Preview
tsx
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@arshad-shah/cynosure-react';
import { useState } from 'react';
export default function Example() {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Open dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Welcome</DialogTitle>
<DialogDescription>
This is a basic dialog with a title and description.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="ghost">Cancel</Button>
</DialogClose>
<Button onClick={() => setOpen(false)}>Got it</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Variants
Section titled “Variants”Confirm dialog
Section titled “Confirm dialog”Use closeOnOverlayClick={false} and a danger-coloured confirm button for destructive actions that require deliberate intent.
Preview
tsx
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@arshad-shah/cynosure-react';
import { useState } from 'react';
export default function Example() {
const [open, setOpen] = useState(false);
const [deleted, setDeleted] = useState(false);
function handleConfirm() {
setDeleted(true);
setOpen(false);
}
return (
<div
style={{ display: 'flex', flexDirection: 'column', gap: '1rem', alignItems: 'flex-start' }}
>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="solid" colorScheme="danger">
Delete account
</Button>
</DialogTrigger>
<DialogContent closeOnOverlayClick={false} size="sm">
<DialogHeader>
<DialogTitle>Delete account?</DialogTitle>
<DialogDescription>
This action is permanent and cannot be undone. All your data will be erased.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="ghost">Cancel</Button>
</DialogClose>
<Button variant="solid" colorScheme="danger" onClick={handleConfirm}>
Yes, delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{deleted && (
<p style={{ fontSize: '0.875rem', color: 'var(--color-fg-danger, #dc2626)', margin: 0 }}>
Account deleted.
</p>
)}
</div>
);
}
With form
Section titled “With form”Embed form controls inside DialogContent. Disable the submit button until input is valid to prevent empty submissions.
Preview
tsx
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
Input,
} from '@arshad-shah/cynosure-react';
import { useState } from 'react';
export default function Example() {
const [open, setOpen] = useState(false);
const [name, setName] = useState('');
const [saved, setSaved] = useState('');
function handleSave() {
setSaved(name);
setOpen(false);
}
return (
<div
style={{ display: 'flex', flexDirection: 'column', gap: '1rem', alignItems: 'flex-start' }}
>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Edit profile</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>Update your display name below.</DialogDescription>
</DialogHeader>
<div style={{ padding: '0 1.5rem' }}>
<Input
value={name}
onChange={setName}
placeholder="Display name"
aria-label="Display name"
/>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="ghost">Cancel</Button>
</DialogClose>
<Button onClick={handleSave} disabled={!name.trim()}>
Save changes
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{saved && (
<p style={{ fontSize: '0.875rem', color: 'var(--color-fg-muted, #6b7280)', margin: 0 }}>
Saved: {saved}
</p>
)}
</div>
);
}
Six sizes are available: xs, sm, md (default), lg, xl, and full. Each sets a different max-width for the content panel.
Preview
tsx
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@arshad-shah/cynosure-react';
import { useState } from 'react';
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
const sizes: Size[] = ['xs', 'sm', 'md', 'lg', 'xl', 'full'];
export default function Example() {
const [activeSize, setActiveSize] = useState<Size | null>(null);
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
{sizes.map((size) => (
<Dialog
key={size}
open={activeSize === size}
onOpenChange={(o) => setActiveSize(o ? size : null)}
>
<DialogTrigger asChild>
<Button variant="outline">{size}</Button>
</DialogTrigger>
<DialogContent size={size}>
<DialogHeader>
<DialogTitle>Size: {size}</DialogTitle>
<DialogDescription>
Dialog at the <strong>{size}</strong> size variant.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="ghost">Close</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
))}
</div>
);
}
PropTypeDefaultDescription
size
"xs"|"sm"|"md"|"lg"|"xl"|"full"
md
Visual size variant. `md` works for most confirm/edit flows;
`lg`/`xl` are for forms; `sm` for compact confirmations.
position
"center"|"top"
center
Vertical placement. `center` for the standard modal feel; `top`
anchors near the top of the viewport.
closeOnOverlayClick
boolean
true
Close when the backdrop is clicked.
closeOnEscape
boolean
true
Close when `Esc` is pressed.
showCloseButton
boolean
true
Render the built-in close button (top-right X).
container
HTMLElement|(() => HTMLElement)
—
Portal target.
closeLabel
string
Close
Accessible label applied to the built-in close button.
hideOverlay
boolean
false
Skip rendering the backdrop. Useful when stacking dialogs or when
the parent already provides a scrim.
Accessibility
Section titled “Accessibility”DialogContentsetsrole="dialog"andaria-modal="true"automatically, and wiresaria-labelledby/aria-describedbyfrom theDialogTitleandDialogDescriptionIDs.- Focus is trapped inside the dialog while it is open; it returns to the trigger on close.
- Pressing
Esccloses the dialog by default (closeOnEscape={true}); suppress withcloseOnEscape={false}. - Clicking the backdrop closes the dialog by default (
closeOnOverlayClick={true}); suppress withcloseOnOverlayClick={false}. DialogTitlemaps toaria-labelledby;DialogDescriptionmaps toaria-describedby— both are required for complete accessible markup.
Recipes
Section titled “Recipes”- For non-destructive confirmations, prefer
AlertDialogwhich is pre-wired for a single confirm/cancel axis. - Use
position="top"to render the panel near the top of the viewport on long pages. - Use
hideOverlaywhen stacking dialogs so only the outermost backdrop is visible. - Wrap the trigger in
Tooltipto add a hover label when the trigger is icon-only.