Live demos — click to open
3 sizes · click scrim or Esc to close
Anatomy
Header · body · footer — static reference
title prop
Lime header bar
Lime header bar
children
Scrollable body
Scrollable body
footer prop
Paper-2 action bar
Paper-2 action bar
Do / Don't
✓ Do
Always provide a clear primary action in the footer and a way to cancel. The modal should confirm a single, focused task.
✕ Don't
Don't nest modals or put navigation inside a modal. Don't use modals for non-blocking information — use Toast or Callout instead.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | false | Controlled open state. Modal unmounts when false. |
| onClose | () => void | — | Called when Esc is pressed or scrim is clicked. |
| title | string | ReactNode | null | Rendered in lime header bar with close ×. Omit for no header. |
| footer | ReactNode | null | Rendered in paper-2 footer bar. Typically action buttons. |
| size | "sm" | "md" | "lg" | "md" | Max-width: sm=480px, md=600px, lg=800px. |
| children | ReactNode | — | Modal body — scrollable, max-height 60vh. |
Usage
import { Modal } from "components/ui/Modal";
const [open, setOpen] = useState(false);
<Modal
open={open}
onClose={() => setOpen(false)}
title="Publish campaign"
size="sm"
footer={
<>
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="accent" onClick={publish}>Publish now</Button>
</>
}
>
Are you sure you want to publish this campaign?
</Modal>