Presentation
Dialog
Give a focused task its own space over the page.
"use client"
import { Button } from "macbetter/button"
import {
Dialog,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "macbetter/dialog"
import { Field, FieldLabel } from "macbetter/field"
import { TextField } from "macbetter/text-field"
export default function Example() {
return (
<Dialog>
<DialogTrigger render={<Button />}>New Event…</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>New Event</DialogTitle>
<DialogDescription>Add an event to your Work calendar.</DialogDescription>
</DialogHeader>
<DialogBody>
<div style={{ display: "grid", gap: 12 }}>
<Field>
<FieldLabel>Title</FieldLabel>
<TextField defaultValue="Design review" />
</Field>
<Field>
<FieldLabel>Location</FieldLabel>
<TextField placeholder="Add a location" />
</Field>
</div>
</DialogBody>
<DialogFooter>
<DialogClose variant="cancel">Cancel</DialogClose>
<DialogClose variant="primary" value="save">
Add
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}When to use it
Use a dialog for a short, self-contained task that people finish or cancel before going back: creating an event, renaming a project, editing a few settings. If the task needs one answer to a question, use an Alert. If it’s quick and belongs to a control, use a Popover. If people should keep using the page while it’s open, use a Sheet inspector.
- Give it a short noun or verb-phrase title (“New Event”), not a sentence.
- Offer a clear way out (Cancel) and one primary action that finishes the task.
Usage
import {
Dialog,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "macbetter/dialog"Write the dialog once. DialogHeader holds the title, DialogBody the content and DialogFooter the buttons. Mark the footer’s buttons with variant="cancel" and variant="primary": on the Mac they line up at the bottom right; on iPhone and iPad they move into the navigation bar as Cancel and Done, around the title. DialogContent brings its own dimmed backdrop.
Sizes
size sets the width on the Mac and iPad: sm (360pt on Mac), md (480pt) or lg (640pt). iPhone always fills the width of the screen.
Presentation
Dialog defaults to presentation="auto": on a phone it becomes a real sheet you can swipe down to dismiss; everywhere else it’s a centered panel. Pass "dialog" or "sheet" to choose. A dialog with unsaved changes can pass dismissible={false} and ask in onDismissAttempt; a swipe then springs back.
Platform behavior
- macOS. A centered panel of thick material, a bold 13pt title, and push buttons at the bottom right: the primary one filled with the accent. It eases in from slightly smaller and above.
- iPadOS. A form sheet: an opaque 540pt card with large corners and a navigation bar of Cancel, the title and Done.
- iOS. An opaque page sheet that rises from the bottom edge and stops just below the status bar, with the same navigation bar. On a phone it can be swiped down.
- The page behind dims with the scrim; the Mac uses a lighter veil, since macOS doesn’t dim a window behind a panel. Reduced motion fades instead of sliding or scaling.
Accessibility
- The content has the
dialogrole andaria-modal, is named byDialogTitleand described byDialogDescription. - Focus moves inside on open, Tab stays inside, and Escape or a press on the dimmed page closes it and returns focus to the trigger.
- Cancel and Done stay real buttons in the navigation bar, in the same Tab order as the footer they come from.
API
Dialog
Takes every ui-sdk Dialog root prop.
| Prop | Type | Default | Description |
|---|---|---|---|
presentation | "auto" | "dialog" | "sheet" | "auto" | auto presents a swipeable sheet on small touch screens. |
open / defaultOpen | boolean | — | Controls or starts the dialog open. |
dismissible | boolean | true | Whether Escape, an outside press or a swipe can close it. |
onOpenChange | (open, details) => void | — | details.returnValue is the value of the DialogClose that closed it. |
DialogContent
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "lg" | "md" | The panel's width on macOS and iPadOS. |
initialFocus / finalFocus | DialogInitialFocus / PopupFocusTarget | — | Where focus goes on open and close. |
DialogClose
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "primary" | "cancel" | "default" | primary and cancel become the navigation bar's Done and Cancel on touch. |
tone | "accent" | "destructive" | "accent" | destructive turns it red. |
value | string | — | Reported as returnValue. |
DialogTrigger, DialogHeader, DialogTitle, DialogDescription, DialogBody and DialogFooter take their element’s props. createDialogController is re-exported from ui-sdk.
| Data attribute | On | Meaning |
|---|---|---|
data-size | .mb-dialog | Its width |
data-presentation | .mb-dialog | dialog or sheet, decided each time it opens |
data-starting-style, data-ending-style | .mb-dialog, .mb-dialog-backdrop | Entering and leaving |
data-variant, data-tone | .mb-dialog-close | Its role and color |
| CSS variable | Default |
|---|---|
--mb-dialog-width-sm, --mb-dialog-width, --mb-dialog-width-lg | 360/480/640px on macOS, 420/540/704px on iPadOS |
--mb-dialog-radius | --mb-popup-radius on macOS, --mb-sheet-radius on touch |
--mb-dialog-height | 620px form sheet on iPadOS |
--mb-dialog-scrim | A lighter --mb-scrim on macOS, --mb-scrim on touch |