Dialog
Display a focused task in an overlay with a title and a clear close action.
Installation
pnpm dlx shadcn@4.18.0 add @siteplane/dialogComplete the one-time setup first. Registry JSON lists source files and dependencies.
For this page’s examples, also install:
pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/data-list @siteplane/form @siteplane/input @siteplane/label @siteplane/menuField is installed with Input.
Usage
import { Button } from "@/siteplane/components/button";
import {
Dialog,
DialogTrigger,
DialogPopup,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
DialogClose,
} from "@/siteplane/components/dialog";
<Dialog>
<DialogTrigger render={<Button />}>Open dialog</DialogTrigger>
<DialogPopup>
<DialogHeader>
<DialogTitle>Project details</DialogTitle>
<DialogDescription>Review the current project.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose render={<Button />}>Done</DialogClose>
</DialogFooter>
</DialogPopup>
</Dialog>;Composition
Dialog
├── DialogTrigger
└── DialogPopup
├── DialogHeader
│ ├── DialogTitle
│ └── DialogDescription
├── DialogPanel
└── DialogFooter
└── DialogCloseDialogPopup includes the portal, backdrop, viewport, popup, and close icon. Do not add a second portal or backdrop. DialogContent and DialogOverlay are the shadcn names of DialogPopup and DialogBackdrop; both keep working.
Size
DialogPopup takes size, the width on the kit's modal scale: xs 400 px, sm 440 px (the default), md 600 px or lg 880 px. Use md or lg for a form with two columns or a wide table. The size applies from 640 px up; below that, every dialog is a full-width sheet at the bottom edge. A max-w-* class in className still sets its own width, also below 640 px. Alert Dialog takes the same sizes.
Open from a menu
Pass open and onOpenChange when something other than DialogTrigger opens the dialog, such as a menu item. Render the dialog outside the menu so it stays mounted after the menu closes.
Nested dialogs
Open a dialog from inside another dialog. The parent stays visible behind it, scaled down and faded by 10 % for each dialog on top, so the context stays in view. Each root owns its state; Escape closes the topmost dialog, and closing it returns focus to its trigger in the parent.
Scrollable content
Put long content in DialogPanel. When the dialog reaches the available height, the panel scrolls while the header and footer stay in view. The dialog opens at the top of its content: when its first focusable element, such as a confirmation field under long terms, starts below the visible part of the panel, the popup itself takes focus, and Tab moves on to that element.
API reference
Dialog
The root renders no DOM element. These properties retain the installed Base UI contract.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | uncontrolled | Controlled open state. |
default | boolean | false | Initial uncontrolled open state. |
on | (open, eventDetails) => void | unset | Open/close request. Details include reason, event, cancel(), and preventUnmountOnClose(). |
on | (open: boolean) => void | unset | Runs after opening or closing animations complete. |
modal | boolean | "trap-focus" | true | true traps focus, locks page scrolling and blocks outside interaction. false permits outside interaction. trap-focus traps focus without the other modal restrictions. |
disable | boolean | false | Prevents outside-press dismissal; also prevents focus-out dismissal in non-modal mode. Escape still closes the dialog. |
actions | RefObject<DialogPrimitive.Root.Actions | null> | unset | Imperative close() and unmount(); manual unmount is for externally managed exit animations. |
handle | Dialog handle | unset | Connects detached triggers and imperative handle methods. |
trigger | string | null | unset | Identifies the active trigger when controlling open state with multiple triggers. |
default | string | null | unset | Initial trigger association, for example with defaultOpen. |
children | ReactNode | ({ payload }) => ReactNode | unset | Parts, or a render function receiving the active trigger payload. |
DialogPopup / DialogContent
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | sm | Popup width from 640 px up: 400, 440, 600 or 880 px. |
show | boolean | true | Renders the built-in close icon action. When false, provide another accessible close action. |
close | DialogPrimitive.Close.Props | unset | Overrides the built-in close action, including aria-label (default: Close). |
portal | DialogPrimitive.Portal.Props | unset | Configures the included portal; container defaults to document.body, keepMounted to false. |
initial | boolean | RefObject | (interactionType) => target | first tabbable element | Focus target when opening. By default the first tabbable element, or the popup itself on touch and when that element starts out of view. |
final | boolean | RefObject | (interactionType) => target | foundation default | Focus target when closing. |
class | string | unset | Adds popup styling. |
render | ReactElement | render function | div | Composes the popup onto another element. |
children | ReactNode | unset | Popup content before the optional built-in close button. |
The styled parts take a string className, which they merge with their own
classes through cn. Style a state through the part's data attributes, such as
data-open, rather than a className function.
The remaining popup props follow Base UI Popup. Keep the portal in the default location unless there is a concrete container requirement. The supplied composition carries the Siteplane UI provider settings into its portal.
Content layout parts
| Prop | Type | Default | Description |
|---|---|---|---|
Dialog | div props + render | div | Groups title/description with header spacing. className adds local styling. |
Dialog | div props + render | div | Lays out actions in a row, aligned to the end. |
Dialog | boolean | true | Enables the scroll area edge fade. |
Dialog | div props + render | div inside ScrollArea | Body wrapped in a ScrollArea; className styles the inner panel. When the content exceeds the available height, it scrolls between the header and footer. |
Dialog | DialogPrimitive.Title.Props | h2 | Accessible dialog title with heading typography: 18 px in the title weight, --sp-rw-font-weight-title (450), which Theming lists. |
Dialog | DialogPrimitive.Description.Props | p | Accessible description with secondary text styling. |
All layout parts accept children, className, native props and render through their corresponding contract. Use React.ComponentProps<typeof DialogPanel> (or another part) to derive its TypeScript props; Siteplane UI does not export separate named prop aliases for these wrappers.
Trigger, close and lower-level exports
| Prop | Type | Default | Description |
|---|---|---|---|
Dialog | DialogPrimitive.Trigger.Props | button | Opens its root. Supports render, disabled, id, handle and payload. |
Dialog | DialogPrimitive.Close.Props | button | Requests closing. Supports render and disabled. |
Dialog | DialogPrimitive.Portal.Props | body / keepMounted=false | Moves content to a container. Already included in DialogPopup. |
Dialog | DialogPrimitive.Backdrop.Props | div | Styled overlay; already included in DialogPopup. |
Dialog | DialogPrimitive.Viewport.Props | div | Styled overlay layout container; already included in DialogPopup. |
Dialog | Base UI Dialog namespace | — | Public foundation namespace for advanced composition and its types. It is not the styled Siteplane UI popup. |
Dialog | () => Dialog handle | — | Creates a handle; retain the instance with state or another stable owner. |
Share a stable DialogCreateHandle between the root and detached triggers; the
root's render function receives the active trigger's payload. The handle
exposes open(triggerId | null), openWithPayload(payload), close(), and
isOpen. Call methods in an event handler or effect after the associated root
has mounted. Controlled roots still need their state updated through
onOpenChange.
Use the ready-made DialogPopup for normal usage. A custom low-level composition must supply its own popup, close action, provider scope and positioning; do not combine a second low-level portal/backdrop with DialogPopup. In particular, setting modal={false} does not remove the backdrop or full-screen viewport included by the styled composition. For an anchored, non-blocking interaction use the appropriate Popover component.
Full inherited part contracts: Root, Trigger, Portal, Backdrop, Viewport, Title, Description, and Close. The installed source is authoritative for the version in your project.
Usage notes
Provide DialogTitle on every dialog, and a description when it adds useful context. Label every form control. Tab and Shift+Tab stay within a modal dialog; Escape requests closing. Always retain an accessible close action, including when hiding the default icon. Verify focus restoration when the original trigger is removed from the page.
Opening focuses the first tabbable element. The popup itself takes focus instead on touch devices, so the on-screen keyboard doesn't open, and when that element starts out of view in a scrolling panel, so a long dialog opens at its top and a screen reader reads its title first. Closing returns focus to the trigger or the previously focused element; use initialFocus and finalFocus to choose other targets.
Submit forms with Button type="submit" and close the dialog only after the submission succeeds; a submit button wrapped in DialogClose would close immediately. Keep the dialog open while an asynchronous request is pending or has failed. To confirm before discarding unsaved changes, see Close confirmation.
When the user must make a decision before going on, use Alert Dialog instead. Keep actions and scrollable content reachable at mobile widths and with zoom. The kit carries shared size, theme, and motion settings through its normal portal; no additional provider is required by these examples.