# Dialog

**Maturity:** stable

A blocking, centered modal surface that interrupts the flow to present a title, an optional description or arbitrary body children, an optional accessory well (one small nested control), and a row of action buttons.

## Import

```tsx
import { Dialog } from "@liquidify/react/dialog"
```

## Props

- `actions`: `readonly DialogAction[]` (required). The action buttons. Exactly two lay out side by side; otherwise stacked.
- `children`: `ReactNode`. Arbitrary body content — the generic-Dialog superset over Alert's single-string `message`.
- `className`: `string | undefined`. Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean.
- `defaultOpen`: `boolean | undefined`. Uncontrolled seed for the open state (default `false`).
- `description`: `string | undefined`. Optional secondary description; the `aria-describedby` target.
- `descriptionProps`: `LabelProps | undefined`. Per-instance typography override for the description (ADR-0028). Spread last.
- `dismissible`: `boolean | undefined`. Whether Escape / a scrim tap dismiss the dialog (forwarded to Overlay). Defaults to `true`.
- `labelProps`: `LabelProps | undefined`. Per-instance typography override for the title (ADR-0028). Spread last.
- `material`: `"frosted" | "regular" | "clear" | undefined`; default `enclosing `<Material default>`, else `"regular"``. Material of the glass surface — `frosted · regular · clear` (§06). Forwarded to the engine, which resolves the chain `prop ?? <Material default> ?? "regular"`, so a bare surface inherits the enclosing provider default.
- `onOpenChange`: `((open: boolean) => void) | undefined`. Fires with the next open state on every close path (action, Escape, scrim).
- `open`: `boolean | undefined`. Controlled open state. Passing it switches Dialog to controlled mode.
- `perf`: `"low" | "high" | undefined`; default `derived from `<Backdrop type>`, else `"low"``. Perf budget of the glass surface (ADR-0006/0007). Forwarded to the engine as `data-glass-perf`; `"high"` opts this surface into the edge-lensing filter. When omitted, derives from the enclosing `<Backdrop type>` (§04).
- `ref`: `Ref<HTMLDivElement> | undefined`. Forwarded ref to the rendered element — the component's native root (or the merged child element on a component that opts into {@link SlottableProps}). Generic over `E` (charter `docs/api-conventions.md` §4 rule 1, default `HTMLElement`) so a component with a polymorphic root (Link's navigation / action mode union, Text's `as`) can instantiate the concrete element per variant instead of leaving every consumer with a widened `Ref<HTMLElement>`. A component that does not narrow simply inherits the `HTMLElement` default — source-compatible with every pre-existing non-generic `extends ControlProps`.
- `size`: `DialogSize | undefined`; default ``"regular"``. Card size — the `small · regular · large` subset of the canonical size scale (Dialog-only; Alert pins `small`).
- `title`: `string` (required). The dialog title — required; the `aria-labelledby` target.

## SwiftUI mapping

- ``isPresented: Binding<Bool>`` → ``open` / `defaultOpen` / `onOpenChange`` (direct): presentation triad (ADR-0015), forwarded to `Overlay`; **open-state only**, not value-bearing. Controlled iff `open !== undefined` (§8); else `defaultOpen` (default `false`) seeds internal state.
- ``titleKey: Text` (the title)` → ``title: string`` (direct): required; bold; the `aria-labelledby` target
- ``message: () -> Text`` → ``description?: string` **or** body `children`` (direct): the `aria-describedby` target. Alert takes a single-string `message`; Dialog generalises the slot to arbitrary `children` (or a convenience `description` string).
- ``actions: () -> View` (Buttons)` → ``actions: DialogAction[]`` (direct): `{ id, label, role?, onPress?, disabled? }`; each action carries a **stable, consumer-supplied `id`** — rows key by `id` (never `role+label`), duplicate labels are supported, a duplicate `id` or an empty collection is a dev-time error; activation runs `onPress` then closes
- ``Button.id` (collection identity)` → ``DialogAction.id: string`` (direct): required; the React key + collection identity (§16.6)
- ``Button(role: .destructive)`` → ``role: "destructive"`` (direct): red label (`--lq-color-action-destructive`), same hue in both schemes
- ``Button(role: .cancel)`` → ``role: "cancel"`` (direct): the Escape / scrim-dismiss default; **neutral weight** (matches render — see States / open question, diverges from Alert's `bold`)
- ``Button(…).disabled(_:)`` → ``disabled?: boolean`` (direct): native `disabled` on `<Button>`, no `aria-disabled`; hard-suppressed (no `onPress`, no close)
- ``actions: { TextField(…) }`` → ``children` (accessory slot)` (direct): one small nested control above the footer (**children role: accessory**)
- ``.font(_:)` / `.foregroundStyle(_:)` on the title` → ``labelProps?: LabelProps`` (direct): shared `Pick<TextProps,…>`; spread **last** over the computed title default (ADR-0028)
- ``.font(_:)` / `.foregroundStyle(_:)` on the message` → ``descriptionProps?: LabelProps`` (direct): spread **last** over the `body`/`secondary` description default
- `(no SwiftUI analogue)` → ``size?: DialogSize`` (web-only): **Dialog-only** — maps the internal Modal width axis onto `SIZE_SCALE` (Alert pins `small`); default `regular`. See Sizes.
- `(presentation chrome)` → ``material?` / `perf?`` (direct): `PresentationalProps` — the card's glass surface; the base is tint-free (actions take the role palette)
- `(no SwiftUI analogue)` → ``dismissible?: boolean`` (web-only): gates the Escape / scrim-tap dismissal (Overlay), default `true`
- `(no SwiftUI analogue)` → ``ref?: Ref<HTMLDivElement>`` (web-only): forwarded to the documented semantic root — the dialog card (Modal's glass `<div role="dialog">`); attached while open, `null` while closed
