组件 · 浮层
Dialog
A centered overlay on a scrim for a decision or a few fields people must finish before they continue, with a wide size for a large form filled in one pass.
import { Dialog } from "@botiverse/raft-ui";A dialog is a centered overlay on a scrim for something people must finish before they continue: a decision, or a small task of a few fields, such as creating a channel. It has a title, a body and at most two buttons. Its wide size holds a large form filled in one pass. To confirm an action, use AlertDialog.
When to use
| Situation | Use |
|---|---|
| A small task in 1 to 5 fields (Create Channel) | Dialog |
| A large form filled in one pass: registering or editing an app, configuring an agent | Dialog with size="wide" |
| Task details | A workspace overlay on Dialog, 960px wide and full screen on a phone, outside the four sizes (Tasks) |
| Confirming an action that cannot be undone, or that interrupts | AlertDialog |
| An action that can be reversed in place | No dialog: do it; when a Toast offers Undo is in Feedback |
| Details or settings people look at and edit for a while | Drawer |
Which overlay fits which job is in Choosing a component.
Anatomy
Dialog: the root. Owns the open state. Required.DialogTrigger: the button that opens it.DialogContent: the centered surface, portaled, with the scrim (DialogOverlay) behind it. Required.DialogHeader: the title row, with an optionalDialogCloseat its end. The × is pulled out so its icon box, not the button, ends on the dialog's 20px margin (Alignment).DialogTitle: the title. Required.DialogDescription: a sentence under the title or in the body.DialogBody: the content. It scrolls when the dialog reaches the window height.DialogFooter: the buttons, aligned to the end.DialogClose: closes the dialog. Without children it is the × icon button; withrenderit turns a Cancel button into a close button.DialogNav: the section list of a wide dialog.DialogSurface: the dialog's frame without Base UI behind it, for a centered modal whose app owns the backdrop, Esc and focus, such as search.DialogPortalandDialogOverlay: lower-level parts.
Variants
Pick the size by what the dialog holds, not by what looks right. The four sizes are far enough apart that each holds one kind of content (cindyz, 2026-10-21):
| Variant | Width | Use for |
|---|---|---|
size="sm" | 400px | A decision: every confirmation and acknowledgement, including one with a few options to choose from or a short list of what it affects (Reset Agent, Change Visibility, the restart options). It is the width of AlertDialog |
size="md" (default) | 448px | A small task: 1 to 5 fields, a list to pick from, a short help text that is only read (Create Channel, Add Members, role help) |
size="lg" | 640px | Content that needs width: a list with several columns, a command or code, key-value rows, a long text to read, a flow of steps (Connect Computer, a computer's release notes) |
size="wide" | 896px | The large form dialog, in sections, with a fixed height of 760px or the window height less 32px, whichever is smaller |
- Every confirmation is one width, 400px, whether it is built on Dialog or AlertDialog. A confirmation whose content no longer fits 400px isn't a confirmation any more: it is a small task, at
md. sm,mdandlgare the small dialog: its height follows its content, up to the window less 32px, and a click on the scrim closes it.wideis the large form dialog.- Set the width with
sizeonly. Product code never passes its ownmax-w-*toDialogContent. Content that fits none of the four sizes isn't a dialog: make it a page or a Drawer. - Only these overlays have a width outside the four sizes, each set by its own rule. Nothing else may add one:
- Task details, 960px: a workspace overlay (Tasks).
- Search, 720px, on
DialogSurface, its top edge over the top bar's field (Search). - The showcase frame, 720px wide and about 520px high, shared by steps 3 to 5 of onboarding and the Upgrade to Pro dialog, and used by nothing else (Onboarding).
- AnnouncementDialog, 576px: its picture and one-paragraph text read best there; at 640px the text runs past 80 characters a line.
- An attachment preview, as wide as the file needs, up to 768px.
- On a narrow screen every size is as wide as itself or the window less 32px, whichever is smaller, so at 390px each one is 358px wide. Its height and the wide dialog's sections are in Behavior.
The large form dialog uses bar on DialogHeader, DialogBody and DialogFooter: the header and footer stay in place and the body scrolls between them. The body holds DialogNav on the left, a line-seam after it, and one FormSection per section on the right. Its structure and rules are in Forms.
Behavior
- A centered dialog appears and disappears without animation, always over the scrim (Motion). On a phone a dialog with fields is a sheet that slides up instead (On a phone).
- Esc, × and Cancel close a dialog. A click on the scrim closes a small dialog.
- Don't close a large form dialog on a click outside it: pass
disablePointerDismissalonDialog, so a half-filled form isn't lost to a stray click. - A centered dialog on a narrow screen keeps a 16px margin on each side and never grows taller than the window less 32px.
- Below 768px the wide dialog hides
DialogNavand the sections follow one another. - A dialog opened from a dialog or a sheet draws its own, lighter scrim over the one below (Stacked overlays). Render it inside the first one and this happens by itself.
layer={1}is only for a dialog rendered outside the first one's tree: it lifts the dialog and its scrim above the first.
On a phone
Below 768px a dialog stays centered unless it has several fields, a long list or steps, in which case it becomes a sheet (stdrc and Artea, 2026-10-21). On a desktop and a tablet every dialog stays centered.
| What the dialog holds | On a phone |
|---|---|
| A decision: a statement and buttons, at most a few fixed choices, nothing typed (every confirmation; archive, stop, reset) | The centered dialog, as on a desktop |
| One input or one text box and its buttons: a confirmation that asks for a typed name, naming a section, editing a display name or a description, a reason to reject | The centered dialog, as on a desktop. While the keyboard is open it moves up, so the keyboard doesn't cover it |
| Several fields, a long or searchable list to pick from, or steps: forms, pickers, wizards | A sheet 90% of the screen tall |
| Content that is only read: role help, release notes, an app's details. It is short information, so it stays a dialog | The centered dialog, as on a desktop |
The sheet:
- Rises from the bottom over the scrim, with
radius-dialogon its top corners. The strip of page left above it shows that it is a layer over the page, not a new page. - Its head is a pushed page's head (Navigation): × at the start, the title, and the dialog's one commit button at the end, at size
mdwith the same color and words as on a desktop. It has no footer: × is Cancel. In a flow of steps the button at the end is the step's (Next, then the last step's action), and × still closes. - The body scrolls under the head and keeps the phone's 16px margin and the home indicator's inset at the bottom.
- × and Esc close it. A tap on the scrim doesn't, so a half-filled form isn't lost to a stray tap, as with a large form dialog on a desktop. When something has been typed, ask before closing.
- Dragging the sheet down to close it is planned, for when the sheet moves into raft-ui on top of Drawer's drag.
- It slides up as it opens, like every bottom sheet (Motion).
Raft Chat builds these forms in the app for now (NarrowDialog in slock); they move into raft-ui as a phone presentation of Dialog.
Content
- Title the dialog with the words of the button that opened it, or Edit and the item's name. Label its main button with the action it performs, without the object: usually the title's verb (Create Channel → Create); an Edit title's button is Save, and the other one Cancel (Writing).
- Set body text in
foreground-muted, below a title inforeground. Their type styles are in Typography.
Accessibility
- Dialog wraps the Base UI Dialog. It provides the
dialogrole, the focus trap, Esc and focus return to the trigger. - Give every dialog a
DialogTitle. It becomes the dialog's accessible name, and it is what a screen reader announces on open. DialogDescriptionbecomes the dialog's description.- The × of
DialogCloseis labeled "Close". Passaria-labelto change it. DialogSurfacehas no behavior of its own. The app that uses it must add thedialogrole,aria-modal, a name, the focus trap, Esc and the scrim.
Rules
- Put at most two buttons in the footer: Cancel on the left (
variant="ghost"), the confirm button on the right. - Make the confirm button pink (
variant="primary") when it does something. Color it by its consequence when it interrupts or can't be undone (AlertDialog). - Put anything people must read before they act in a Banner inside the body (Feedback).
- Pad every size by 20px. Its width comes from
size(Variants), never from a class of your own. - Don't pass
overlay={false}in product code: every centered dialog has the scrim, without exception.
API
Dialog
| Prop | 类型 | 默认值 |
|---|---|---|
| A ref to imperative actions. - `unmount`: Manually unmounts the dialog. Call this after any externally controlled closing animation finishes. - `close`: Closes the dialog imperatively when called. | RefObject<DialogRootActions | null> | |
| The content of the dialog. This can be a regular React node or a render function that receives the `payload` of the active trigger. | ReactNode | PayloadChildRenderFunction<unknown> | |
| Whether the dialog is initially open. | boolean | false |
| ID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog. | string | |
| Whether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it. | boolean | false |
| A handle to associate the dialog with a trigger. If specified, allows external triggers to control the dialog's open state. Can be created with the Dialog.createHandle() method. | BaseDialog.Handle<unknown> | |
| Determines if the dialog enters a modal state when open. - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled. | boolean | "trap-focus" | true |
| Event handler called when the dialog is opened or closed. | (open: boolean, eventDetails: BaseDialog.Root.ChangeEventDetails) => void | |
| Event handler called after any animations complete when the dialog is opened or closed. | (open: boolean) => void | |
| Whether the dialog is currently open. | boolean | |
| ID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the `open` prop is not set). | string |
DialogBody
| Prop | 类型 | 默认值 |
|---|---|---|
| A pinned bar of a scrolling (`size="wide"`) dialog. | boolean | false |
Also takes the attributes of its element.
DialogClose
| Prop | 类型 | 默认值 |
|---|---|---|
| ReactNode | ||
| `md` (32px) and `icon-md` for rows, cards, banners, toolbars and panel headers; `lg` and `icon-lg` (the control height) for dialog footers, forms and beside a field; `xl` for a full-width call to action. The size follows the place: see Button › Size by place. | "md" | "lg" | "xl" | "icon-md" | "icon-lg" | DeprecatedButtonSize | "md" |
| "primary" | "accent" | "warning" | "danger" | "default" | "outline" | "information" | "muted" | "success" | "danger-secondary" | "danger-outline" | "ghost" | "on-frame" | ||
| Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | boolean | true |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
DialogContent
| Prop | 类型 | 默认值 |
|---|---|---|
| 0 | 1 | ||
| false | DialogOverlayProps | ||
| AlertDialogPortalProps | ||
| "sm" | "md" | "lg" | "wide" | "md" | |
| Determines the element to focus when the dialog is closed. | boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void) | |
| Determines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard. | boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | HTMLElement | null | void) | |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
DialogDescription
| Prop | 类型 | 默认值 |
|---|---|---|
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
DialogFooter, DialogHeader
| Prop | 类型 | 默认值 |
|---|---|---|
| A pinned bar of a scrolling (`size="wide"`) dialog. | boolean | false |
Also takes the attributes of its element.
DialogNav
| Prop | 类型 | 默认值 |
|---|---|---|
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
DialogOverlay
| Prop | 类型 | 默认值 |
|---|---|---|
| 0 | 1 | ||
| Whether the backdrop is forced to render even when nested. | boolean | false |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
DialogPortal
| Prop | 类型 | 默认值 |
|---|---|---|
| A parent element to render the portal element into. | HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | |
| Whether to keep the portal mounted in the DOM while the popup is hidden. | boolean | false |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
DialogSurface, DialogTitle
| Prop | 类型 | 默认值 |
|---|---|---|
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
DialogTrigger
| Prop | 类型 | 默认值 |
|---|---|---|
| A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. | BaseDialog.Handle<unknown> | |
| ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop). | string | |
| Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | boolean | true |
| A payload to pass to the dialog when it is opened. | {} | |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
AnnouncementDialog
| Prop | 类型 | 默认值 |
|---|---|---|
| A ref to imperative actions. - `unmount`: Manually unmounts the dialog. Call this after any externally controlled closing animation finishes. - `close`: Closes the dialog imperatively when called. | RefObject<DialogRootActions | null> | |
| The content of the dialog. This can be a regular React node or a render function that receives the `payload` of the active trigger. | ReactNode | PayloadChildRenderFunction<unknown> | |
| Whether the dialog is initially open. | boolean | false |
| ID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog. | string | |
| Whether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it. | boolean | false |
| A handle to associate the dialog with a trigger. If specified, allows external triggers to control the dialog's open state. Can be created with the Dialog.createHandle() method. | BaseDialog.Handle<unknown> | |
| Determines if the dialog enters a modal state when open. - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled. | boolean | "trap-focus" | true |
| Event handler called when the dialog is opened or closed. | (open: boolean, eventDetails: BaseDialog.Root.ChangeEventDetails) => void | |
| Event handler called after any animations complete when the dialog is opened or closed. | (open: boolean) => void | |
| Whether the dialog is currently open. | boolean | |
| ID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the `open` prop is not set). | string |
AnnouncementDialogTrigger
| Prop | 类型 | 默认值 |
|---|---|---|
| A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. | BaseDialog.Handle<unknown> | |
| ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop). | string | |
| Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | boolean | true |
| A payload to pass to the dialog when it is opened. | {} | |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
AnnouncementDialogClose
| Prop | 类型 | 默认值 |
|---|---|---|
| ReactNode | ||
| `md` (32px) and `icon-md` for rows, cards, banners, toolbars and panel headers; `lg` and `icon-lg` (the control height) for dialog footers, forms and beside a field; `xl` for a full-width call to action. The size follows the place: see Button › Size by place. | "md" | "lg" | "xl" | "icon-md" | "icon-lg" | DeprecatedButtonSize | "md" |
| "primary" | "accent" | "warning" | "danger" | "default" | "outline" | "information" | "muted" | "success" | "danger-secondary" | "danger-outline" | "ghost" | "on-frame" | ||
| Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | boolean | true |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
相关组件
改动
- 0.8.3 · FixedA
DialogorAlertDialogopened from a sheet or another dialog draws its own, lighter scrim, so it no longer sits undimmed on the surface below. A sheet opened from a dialog does the same; a sheet over a sheet is unchanged. Nesting follows the modal roots, as in Base UI, and amodal={false}parent doesn't count. - 0.6.0 · Breaking ChangesProps that no longer change anything are removed:
Badgeuppercase;Checkboxsize,variant,color;Statuspulse,attention(status never animates);Switch/SwitchThumbsize;SidebarItemvariant;SidebarItemCount/SidebarItemIndicatorvariantandappearance(the count is always the pink sticker, the indicator the yellow @);NotificationCenterActionButtonvariant;DrawerContentwidth(one-go forms use the wideDialog);AppRailItemIndicatorattention;AppRailItemAttentionchildren;Stepssize,track,renderIndicator,renderTraveler;FilePreviewBadgetype,color. Remove them at call sites. - 0.5.28 · AddedAdded the composable
AnnouncementDialogthroughraft-ui/wip, built on existing Dialog and Button primitives, with a label, picture, scrolling body and fixed footer. Narrow dialogs stack both actions at full width.