Dialog
A modal confirmation surface. Native presentation on every platform; shared intent, tokens, and naming.
Installation
npx felicityui add dialog --platform reactUsage
import { Button } from "@/components/ui/button"
import { Dialog, DialogFooter } from "@/components/ui/dialog"
<Dialog
open={open}
onOpenChange={setOpen}
title="Are you absolutely sure?"
description="This action cannot be undone. This will permanently delete the draft."
>
<DialogFooter>
<Button variant="ghost" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button onClick={() => setOpen(false)}>Continue</Button>
</DialogFooter>
</Dialog>Composition
Use the following composition to build a Dialog.
Dialog
├── title
├── description
└── children
├── DialogBody
│ ├── Name
│ └── Username
└── DialogFooter
├── Cancel
└── ConfirmUse Dialog to confirm or dismiss a short decision. Do not use it for navigation, forms that scroll, or a persistent side panel — those belong to Drawer / Sheet.
When to use
- Confirm a destructive action (delete, discard, sign out).
- Acknowledge a blocking choice with one or two actions.
- A couple of short fields (name, username) in
DialogBody, with actions inDialogFooter. - On the web only: a short scrolling list inside
DialogBody, with actions pinned inDialogFooter.
When not to use
- Long settings or a scrolling profile editor. Use Drawer.
- Non-modal help or inline validation.
- Navigation. Use the platform's navigation primitives.
Confirm
Two actions plus a couple of short fields. Put fields in DialogBody on the web. SwiftUI uses TextField in the alert; Compose puts fields in the AlertDialog text slot.
Destructive
Irreversible confirm. Delete stays disabled until the typed phrase matches. Native destructive role on the confirm action.
Custom dismiss
Replace Cancel with your own dismiss action. Dialog does not ship a close icon — actions are children.
Share link
Single Done action with a copyable MCP URL in the field. Omit Cancel. Native share uses ShareLink / the system share sheet.
Scrollable content
On the web, DialogBody scrolls while the title and DialogFooter stay put. SwiftUI and Compose keep this as the system alert message — longer layouts belong on Drawer.
RTL
Pass dir="rtl" on the React dialog. SwiftUI and Compose follow the system layout direction.
API Reference
Dialog
| Prop | Type | Default | Description |
|---|---|---|---|
| open / isPresented | boolean / Binding<Bool> | — | Whether the dialog is shown. SwiftUI uses isPresented. |
| onOpenChange / onDismiss | (open: boolean) => void / () -> Unit | — | Called when the user dismisses. React uses onOpenChange; Compose uses onDismiss. |
| title | string | — | Accessible name. Required. |
| description / message / text | string | — | Optional supporting copy. Named description (React), message (SwiftUI), text (Compose). |
| children | ReactNode | — | Optional DialogBody plus DialogFooter actions. Compose Button here; Dialog does not import it. |
| confirmTitle / confirmLabel | string | OK | Confirm action label on SwiftUI and Compose. |
| cancelTitle / cancelLabel | string | null | Cancel | Dismiss action label on SwiftUI and Compose. Pass nil / null to omit the dismiss action. |
| destructive | boolean | false | Marks the confirm action as dangerous. |
| onConfirm | () -> Void / () -> Unit | — | Confirm handler on SwiftUI and Compose. |
| className | string | — | Additional classes on the dialog element. |
| dir | "ltr" | "rtl" | — | Text direction on the React dialog. Native platforms follow the system layout direction. |
Accessibility
WCAG 2.2 AA
- Must be exposed as a dialog (or the platform alert equivalent), not a custom overlay
div. - Title is the accessible name.
- Description, when present, is the accessible description.
- Focus moves into the dialog when it opens and returns to the invoker when it closes.
- Escape or the platform dismiss gesture closes a non-required dialog.
- Confirm and cancel are real buttons with visible names.
Platform notes
React
Renders a native HTML <dialog> with showModal(). Focus trap, Escape, and backdrop dismiss come from the element, not a portal overlay.
Client Component:showModal()must run in the browser.- Compose actions with Button in children. Dialog does not import Button.
- Prefer
type="button"on actions inside the dialog. - DialogBody holds a short list or a couple of fields; DialogFooter stays put. Longer forms belong on Drawer.
- Pass dir="rtl" on the dialog for RTL. There is no close icon — dismiss is a composed action.
- The ::backdrop is a dark translucent dim. Native modal dialogs cover the whole viewport, including chrome; the page stays visible through the overlay.
- Document scroll is locked while the dialog is open. DialogBody may still scroll.
SwiftUI
SwiftUI .alert modifier. Not a web-style card. Destructive confirm uses Role.destructive.
VoiceOverannounces the system alert title and message.Dynamic Typeis the system alert type size.- Omit cancelTitle (pass nil) for a single confirm action.
- Short prompts can add TextField in the alert actions. Richer layouts belong on Sheet / Drawer.
Compose
Material3 AlertDialog with FelicityUI colors. Interaction (scrim, back, talkback) stays platform-native.
TalkBackannounces the title.- Back and scrim call onDismiss.
- Omit cancelLabel (pass null) for a single confirm action.
- A couple of OutlinedTextField rows can live in the text slot. Do not restyle this into a web card; use a sheet for longer content.