Dialog
The panel of a modal, and nothing else. Put it inside a Layer, mount a Header on top of it, and you have a dialog.
API
Props
| prop | type | default | |
|---|---|---|---|
as? | ElementType | "div" | the element or component to render |
className? | string | — | merged over the defaults, so it wins |
…rest | props of as | — | typed against the chosen element |
Dialog.Body
The only part that scrolls: it takes the space the header and the footer leave.
| prop | type | default | |
|---|---|---|---|
as? | ElementType | "div" | the element or component to render |
className? | string | — | merged over the defaults, so it wins |
…rest | props of as | — | typed against the chosen element |
Dialog.Footer
The actions, aligned to the end and separated by a rule.
| prop | type | default | |
|---|---|---|---|
as? | ElementType | "div" | the element or component to render |
className? | string | — | merged over the defaults, so it wins |
…rest | props of as | — | typed against the chosen element |
Examples
Usage
A dialog is three components stacked: Layer is the behaviour, Dialog is the surface, Header is the bar on top. None of them knows about the others, which is why the same Header works on a sheet or on a mobile screen.
const { close, id, isActive, open } = useGraftDialog<void>();
<Layer open={isActive} onClose={close} aria-labelledby={id}>
<Dialog className="max-w-lg">
<Header className="border-b-2 border-border">
<Header.Title id={id} title="Delete invoice" />
<Header.Trailing>
<Button aria-label="close" onClickBlur={close}>
<Button.Icon icon={<MdClose />} />
</Button>
</Header.Trailing>
</Header>
<Dialog.Body>This cannot be undone.</Dialog.Body>
<Dialog.Footer>
<Button onClickBlur={close}>
<Button.Label label="cancel" />
</Button>
</Dialog.Footer>
</Dialog>
</Layer>Details
On top of Layer
Dialog is a surface: it does not open, close or trap anything. All of that is Layer, which has its own page and can carry a panel of your own just as well.
Naming the dialog follows from that split: pass an id to Header.Title and the same id as aria-labelledby on the Layer. Nothing is generated behind your back, and a dialog with no visible title can be named with aria-label instead.
The panel is a column
The panel is a column: it is capped at the height of the layer, hides its own overflow, and lets Dialog.Body take the remaining space. That is what keeps a header and a footer fixed while only the middle scrolls — and it is all the layout the component owns.
The footer aligns its actions to the end, which is the common case and not the only one: a destructive action set apart from the confirming one wants the two pushed to opposite sides, and that is a className.
<Dialog.Footer className="justify-between">
<Button onClickBlur={remove}>
<Button.Label label="delete" />
</Button>
<Button active onClickBlur={close}>
<Button.Label label="done" />
</Button>
</Dialog.Footer>