Dialog
A modal surface that takes the window until it is dismissed.
Dialog dims and blocks the window, moves focus into its surface, keeps Tab
inside it, and returns focus to whatever opened it. Compose it from a trigger, a
content surface and the header, description and footer parts.
Browser preview: the geometry, palette and states of the native component. The shipped component renders through GPUix, not the DOM.
Installation
nub add @gpuixui/components@workspace:*Run this inside the GPUIXUI workspace. The packages are private and not published to a registry, so the workspace protocol is the version. A fresh checkout installs everything with nub install.
Usage
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@gpuixui/components/dialog";
import { Button } from "@gpuixui/primitives";
<Dialog>
<DialogTrigger>Delete workspace</DialogTrigger>
<DialogContent label="Delete workspace">
<DialogHeader>
<DialogTitle>Delete this workspace?</DialogTitle>
<DialogDescription>
The workspace and its 42 files are removed for everyone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose>Cancel</DialogClose>
<Button variant="destructive">Delete workspace</Button>
</DialogFooter>
</DialogContent>
</Dialog>;Composition
| Part | Responsibility |
|---|---|
Dialog | owns open/defaultOpen/onOpenChange and the dismiss layer |
DialogTrigger | a button that toggles the dialog, with aria-expanded |
DialogContent | the veil and the surface: focus, Tab, Escape and outside press |
DialogHeader | the title and description block |
DialogTitle | the heading, announced at level 2 |
DialogDescription | one paragraph of supporting copy |
DialogFooter | the right-aligned actions row |
DialogClose | a button that closes the enclosing dialog |
The parts validate their placement: a DialogTitle outside DialogHeader, or a
DialogContent outside Dialog, fails with a message naming the family. Put
DialogTrigger and DialogContent directly inside Dialog.
Controlled and uncontrolled
// Uncontrolled: the dialog remembers.
<Dialog defaultOpen>…</Dialog>;
// Controlled: the app remembers, which async work needs.
<Dialog open={open} onOpenChange={setOpen}>…</Dialog>;The controlled form is authoritative. onOpenChange still fires, but the dialog
does not change its own state until the consumer re-renders with the new value.
A dialog doing async work stays open while the work runs and closes only when it
succeeds.
Sizes
DialogContent takes size, a width cap in pixels:
| Size | Width |
|---|---|
sm | 360px |
md (default) | 480px |
lg | 640px |
The surface never exceeds the window minus two spacing["3xl"], and its interior
scrolls, so a long body cannot push the actions off screen.
Dismissal
dismissible (default true) controls both Escape and an outside press. A dialog
doing uninterruptible work sets dismissible={false} and re-enables it when the
work settles.
Open overlays share one dismiss stack: Escape and outside presses are consumed by the topmost layer only. A popover inside a dialog closes the popover first; one event cannot close both.
Keyboard
| Key | Effect |
|---|---|
Tab | next focusable inside the surface, wrapping at the end |
Shift+Tab | previous focusable, wrapping at the start |
Escape | close when dismissible; focus returns to the opener |
Enter / Space | activate the focused action |
API reference
Dialog
| Prop | Type | Default |
|---|---|---|
open | boolean | uncontrolled |
defaultOpen | boolean | false |
onOpenChange | (open: boolean) => void | none |
dismissible | boolean | true |
DialogContent
| Prop | Type | Default |
|---|---|---|
label | string (required) | none |
size | "sm" | "md" | "lg" | "md" |
showClose | boolean | true |
initialFocus | () => number | null | none |
DialogTrigger, DialogClose and DialogTitle take the GPUix host props where
they render a host element plus, for the buttons, the button variant and
size. Every part merges style last.
Accessibility and limitations
role="dialog"with the requiredlabelas its accessible name.- Focus enters the surface on open and returns to the opener on close.
- No
aria-modaland noaria-labelledbyin@gpuix/native@0.10.0. The dialog is named bylabel; its title is a separate heading, not the dialog's accessible name. - The veil is an anchored layer at window coordinates. 0.10.0 ships no
portal, and
position: "absolute"resolves against the mount parent, so mount a dialog at or near the root of the tree. - The top-right close control is a
✕text glyph until an icon package lands. - A dialog is not resizable, draggable or multi-window. One dialog per surface.
Related
- Confirmation dialog — the safe default for destructive actions.
- Popover — a non-modal floating surface anchored to a trigger.
- Card — the in-flow surface, when nothing should be blocked.