All components (204)
Dialog
Layers · Dialog · .amset-dialog
What it is and when to use it
A modal dialog: it blocks progress until it is answered. It is a native <dialog> opened with showModal(): it moves focus inside on opening, keeps it there while open, makes the rest of the page inert and returns focus to the trigger on closing.
When to use it
- A decision that cannot be ignored, or a short form that makes sense without leaving the screen.
kind="alert"for destructive confirmations: it is announced asalertdialogand does not close when clicking outside, so a stray click does not answer on anyone's behalf. Escape does close it, as a cancellation (the WAI-ARIAalertdialogpattern).- With
triggerit opens and closes on its own; without it, whoever uses it drives it withopenandonOpenChange. actionsreceivesclose, so the action button closes it without keeping the state outside;footerStartputs something on the left of the footer.loadingwhile its content is arriving: the body hasaria-busyand saysloadingText("Loading…").dismissable={false}for a half-filled form that must not be lost by a click outside.
When not to
- An "are you sure?" attached to the button:
ConfirmAction. If the question is asked from code,useConfirmation. - Filters or the detail of a row, which slide in from a side:
Drawer. - A panel attached to its button, without a backdrop:
Dropdown. - If it needs a lot of scrolling, it is probably a page.
Keyboard: on opening, focus enters; Tab and Shift+Tab stay inside. Escape closes and focus returns to the trigger; in kind="alert" it counts as cancel and reaches onOpenChange(false). Clicking outside closes only the regular one.
Accessibility: it is announced as dialog (or alertdialog) with the title as its name, and description as its accessible description. The X appears with withCloseButton, never on alert dialogs, and is named with closeLabel ("Close the dialog"). The icon of the tone is decoration: the title has to say everything. onClosed arrives when the dialog has already gone (after the exit animation) and focus has already returned: it is the place to move focus elsewhere or to announce something.
Fine-tuning: CSS variables --amset-dialog-*: -width, -padding, -radius, -background and -slot; the backdrop, --amset-overlay-background and --amset-overlay-padding. What is stacked comes from the --amset-layer-* scale (modal is 100).
Custom header: icon (with the color of accent) in place of the tone's, headerActions between the title and the X, and title can be a node. width gives a custom width (a number is pixels, a string is a CSS measure) and takes precedence over size; placement="top" pins it to the top, for what grows downward like a palette; hideHeader hides the header without losing the accessible name; flush removes the body padding and bodyColumn makes it a flex column, for content with its own scrolling.
Embedded: with inline the same box is rendered IN the page, as a region with its title: no <dialog>, no backdrop, no Escape and no focus trap, and no X. It is useful for reusing as a screen panel what is a dialog on another. open shows or hides it.
What scrolls is the body: the header and the footer stay still, and the lines that separate them appear only if the body does not fit entirely. With the whole dialog scrolling, in a long one the title went off the screen (measured: −563 px after scrolling 600) and the buttons only appeared on reaching the end.
What it looks like
<Dialog
title="Delete the order"
description="This cannot be undone."
trigger={<Button variant="soft" tone="error">Delete</Button>}
actions={(cerrar) => <Button variant="solid" tone="error" onClick={cerrar}>Yes, delete</Button>}
>
<Text>The shipping history will be lost.</Text>
</Dialog>Props
Import it with import { Dialog } from '@amset/ui'. Required props are marked.
| Prop | Type | What it does |
|---|---|---|
titlerequired | ReactNode | The title. Text, or a node if it carries something more (a mark, a count): the accessible name of the dialog comes from its text content. |
accent | string | An own CSS colour for the header (the icon) when neither a tone nor the brand accent will do: a section with its own colour. It is written in `--amset-dialog-accent`. |
actions | (close: () => void) => ReactNode | Footer buttons. It receives `close` so the action can close the dialog without whoever uses it having to keep the state. |
bodyColumn | boolean | The body as a flex column: the content shares out the height and carries its own scroll area, instead of the whole body scrolling. |
children | ReactNode | Body of the dialog. Short text: if it needs scrolling, it is probably a page. Without a body (a question that fits in the title and the description) the gap is not painted. |
className | string | Merged with the library classes; it does not replace them. |
closeLabel | string | — |
description | ReactNode | Sentence under the title, before the body. It is used as the accessible description. It can carry more than text (a status label): its text is read. |
dismissable | boolean | Pressing outside closes. By default yes, except in `kind="alert"`, which is never closed that way. With `false`, the dialog only goes away through its buttons, Escape or `open`: for a half-filled form that must not be lost by one click too many (antd's `maskClosable`). |
flush | boolean | The body without padding: content that already brings its own, or that reaches the edges. |
footerStart | ReactNode | Extra content on the left of the footer: a help link, a checkbox. |
full | boolean | Takes up the whole window minus a margin: for an editor or a preview. |
headerActions | ReactNode | Buttons in the header, between the title and the close cross: a help, a menu. |
hideHeader | boolean | No visible header: the title is still the accessible name, but it is not painted. For what carries its own heading inside, such as a search box. |
icon | ReactNode | Own icon for the header, instead of the tone's. It inherits the colour of `accent` if given, and otherwise the text colour. |
inline | boolean | The same box painted IN THE PAGE: no `<dialog>`, no backdrop, no Escape or focus trap and no cross. To reuse as a screen panel what elsewhere is a dialog. `open`, if given, shows or hides it. |
keyboardDismissDisabled | boolean | Escape closes. By default yes. If both are turned off, what is INSIDE has to offer a way out, or whoever does not use a mouse is locked in. |
kind | "alert" | "normal" | `alert` for destructive confirmations: it is announced as `alertdialog` and does not close when clicking outside, so that a stray click does not answer on anyone's behalf. Escape does close it, as a cancellation (the WAI-ARIA `alertdialog` pattern): it reaches `onOpenChange(false)`, just like the cancel button. |
loading | boolean | The content is arriving. A skeleton is shown instead of `children`, and the body is marked busy. The title and the buttons stay: whoever opened it knows what it is about and can close it without waiting. |
loadingText | string | What is announced to the reader while loading. "Loading…" by default. |
onClosed | () => void | Runs when it has finished closing: after the exit animation, with the dialog already out of the page and focus returned. Also when whoever controls it closes it with `open={false}`. |
onOpenChange | (open: boolean) => void | — |
open | boolean | — |
placement | "center" | "top" | Where it is placed: in the centre, or stuck to the top (the command palette, which grows downward with its results without moving). |
size | "sm" | "md" | "lg" | Maximum width: 24, 32 or 44 rem. |
tone | "info" | "success" | "warning" | "error" | Color and icon of the header, for confirmations and warnings. Without a tone, the header has no icon. |
trigger | ReactElement<unknown, string | JSXElementConstructor<any>> | Element that opens it. If not passed, the dialog is controlled with `open`. |
width | string | number | An own maximum width, beyond `size`: a number is pixels and a string is a CSS measure (`'60rem'`). It wins over `size`. |
withCloseButton | boolean | Cross at the top right. It is not shown in an alert dialog: there the decision is taken with the buttons, not by escaping through the corner. |
It also accepts the attributes of the element it renders: className, style, data-*, aria-* and its events.
CSS variables
These CSS variables are how you adapt it. Declare them on the component's selector, not on :root: the stylesheet redeclares them in the rule itself, and a declaration on the same element always wins.
| Variable | Default |
|---|---|
--amset-dialog-width | 32rem |
--amset-dialog-padding | var(--amset-space-5) |
--amset-dialog-radius | var(--amset-radius-lg) |
--amset-dialog-background | var(--amset-surface) |
--amset-dialog-slot | var(--amset-space-3) |
--amset-dialog-accent | currentColor |
How to change it
.amset-dialog {
--amset-dialog-width: ...;
--amset-dialog-padding: ...;
}In the test bench
Every state and variant, to try one at a time, on its page at ui.amset.tech:
