Dialog
StableA modal window on the native dialog element, with a title, a body that scrolls and a footer for buttons.
Also known as modal, modal dialog, popup window, alert dialog
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Confirm with a footer
function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="secondary" onClick={() => setOpen(true)}>Publish changes</Button>
<Dialog open={open} onClose={() => setOpen(false)} title="Publish these changes?" description="People with the link will see the new version right away."
footer={<>
<Button variant="secondary" onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={() => setOpen(false)}>Publish</Button>
</>}>
<p>You can go back to the earlier version from the history list.</p>
</Dialog>
</>
);
}The simplest use: a title, a line of context and two buttons. The main action goes last.
When to use it
Stops the person to ask for a decision or a small amount of input that must be settled before they go on. Everything behind it is locked until it closes.
Made of: Backdrop · Panel · Danger label (destructive only) · Title · Description · Close button · Body · Footer
Use it for
- Confirming an action that is hard to undo, such as deleting a project.
- A short form that must be finished before moving on.
- Long content that should stay out of the page until asked for, such as terms.
Do and avoid
Do
- Keep the open value in your own code and set it to false in onClose.
- Name the footer buttons after what they do.
- Put one clear main action in the footer and make the way out equally easy to find.
- Keep the body short. If it needs a full page, make it a page.
- Use tone destructive for actions that cannot be undone.
Avoid
- Do not open a Dialog for a message that needs no answer. Use a Toast or a Callout.
- Do not stack one Dialog on top of another.
- Do not use it for a short list of choices. Use a Popover or a menu.
- Do not set dismissible to false unless leaving without choosing would cause harm.
- Do not rely on the red tone alone to warn. The label and your wording must say it too.
On a phone
- On a phone the dialog opens from the bottom as a sheet with rounded top corners; from 640px it is centered.
- It is at most 90% of the screen height, the body scrolls inside, and scrolling does not pass through to the page.
- The footer buttons stack in a column on a phone, sit side by side from 640px, and stay clear of the bottom safe area.
- Tapping the backdrop closes it unless you made it not dismissible.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| openrequired | boolean | none | Whether the dialog is showing. Your code owns it. |
| onCloserequired | () => void | none | Called when the person asks to close: Escape, a click on the backdrop or the Close button. Set open to false in response. |
| titlerequired | string | none | Rendered as the h2 heading and used as the dialog's name through aria-labelledby. |
| description | string | none | Shown under the title and linked with aria-describedby. |
| children | ReactNode | none | The body. It scrolls when it is too tall. |
| footer | ReactNode | none | Usually buttons. They sit in a bar under the body, stacked on phones and in a row from the sm breakpoint. |
| size | "sm" | "md" | "lg" | "md" | Maximum width from the sm breakpoint up: 24rem, 32rem or 42rem. On phones it is always full width. |
| dismissible | boolean | true | When false, Escape and backdrop clicks do nothing and the Close button is hidden. Only your footer buttons can close it. |
| tone | "default" | "destructive" | "default" | Destructive adds a danger icon and the word Destructive above the title, and sets role="alertdialog". |
| className | string | none | Classes added to the panel. |
"Show code under the preview gives the code for the example on the stage."
States
States this part has beyond its default look. Build each one your screen can reach. States set by a property are in the Code tab.
- closed
- With open false nothing is drawn and the page behaves as normal.
- open
- The page behind is inert, cannot scroll, and focus moves into the window. Focus returns to the opener once it has gone.
- closing
- After open turns false the panel fades out in --dur-fast and cannot be pressed. It stays modal until it has gone. Under reduced motion it goes at once.
- scrolling body
- When the body is taller than the window, only the body scrolls and the title and footer stay in view.
- bottom sheet
- Under the sm breakpoint the panel is full width with rounded top corners and sits at the bottom of the screen.
- focus
- A visible focus ring shows on the Close button, on footer buttons and on controls in the body when reached by keyboard.
Tokens
Change one of these and every part that uses it follows.
--surface-raised--border--scrim--shadow-lg--danger-fg--fg--fg-muted--dur-fast--dur-base--dur-slow--focus-ring--radius-overlay
Built in
- It uses the native dialog element opened with showModal, so the page behind is inert and Tab stays inside the window.
- The title is the dialog's name and the description is read after it. A destructive Dialog has role alertdialog.
- Focus moves to the first control in the body or footer, or the dialog itself when there is none, and returns to the element that opened it once the dialog has gone.
- Escape asks to close through onClose. With dismissible false it does nothing.
- The Close button is labelled "Close" and is 44px on touch screens.
- The danger icon is hidden from screen readers; the word Destructive carries the meaning for everyone.
- The page behind cannot scroll while the dialog is open.
- On phones the panel is a bottom sheet with a maximum height in dvh units, so the on-screen keyboard and browser bars do not hide the footer. Movement only plays when motion is not reduced.
- Closing is a quick plain fade in --dur-fast, faster than opening. The panel cannot be pressed while it fades, and the dialog stays modal until it has gone. When people turn off motion in their system settings, it goes at once.
What you need to do
- Write a title that says what is being decided, such as "Delete this project?". The title is the dialog's name.
- Label the footer buttons with the outcome ("Delete project"), not Yes and No.
- Put the safe choice first in the reading order, and never make the destructive button the only way to leave.
- If you set dismissible to false, give a footer button that closes it.
- A toast raised while a Dialog is open sits behind the inert page. Show it after the Dialog closes.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide