<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Dialog

A dialog that presents itself for the screen: a centered dialog on wider screens and a bottom sheet you can drag away on phones (below 640px). It handles focus, Escape, outside presses, scroll locking and a close button.

```tsx
import { Dialog } from "@/components/ouro/dialog/dialog";
```

## Example

```tsx
<Dialog title="Edit recipe" trigger={<Button>Edit</Button>}>
  <RecipeForm />
</Dialog>
<Dialog
  title="Save to cookbook"
  open={open}
  onOpenChange={setOpen}
  footer={<Button onClick={() => setOpen(false)}>Done</Button>}
>
  <CookbookList />
</Dialog>
```

## Usage

Use `trigger` for a dialog a button opens, or `open` with `onOpenChange` to control it.

It stacks above the page at `--ouro-layer`, a z-index of 50 by default (from ouro-base.css). If your app has fixed bars above z-50, raise it in a `:root` rule of your own after the Ouro tokens, outside any `@layer`: `:root { --ouro-layer: 60; }`. Don't edit the `--ouro-layer: 50` line shadcn wrote; `shadcn add` resets it.

Testing accessibility: in WebKit, Base UI's focus guards take `role="button"` without a name so VoiceOver's cursor stays trapped. Exclude them: `new AxeBuilder({ page }).exclude("[data-base-ui-focus-guard]")`.

## Use instead when

- Use `ConfirmDialog` instead to confirm an action, and `Sheet` instead when the panel should slide in from an edge at every width.

## Props

The props the modes share. Type: `DialogProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  | The body. It scrolls when the dialog is taller than the screen. |
| `className` | `string` |  | Layout only, applied to the panel: margins (`mt-4`). Use `size` for width. Your classes win over Ouro UI's. Appearance classes warn in development. |
| `closeLabel` | `string` | `"Close"` | The label of the close button. A centered dialog shows it as an icon button in the top corner; as a sheet it is hidden until it has keyboard focus. Screen-reader users on phones (who have no Escape key) can always reach it. |
| `description` | `ReactNode` |  | A short line under the title, read out when the dialog opens. |
| `footer` | `ReactNode` |  | The actions row: a row at the end on wider screens, stacked full width on phones. Put the primary action last, so it sits on the right on wider screens and at the bottom, nearest the thumb, on phones: `footer={<><Button variant="outline" tone="secondary">Cancel</Button><Button>Save</Button></>}`. |
| `hideTitle` | `boolean` |  | Hides the title visually. It still names the dialog for screen readers. |
| `presentation` | `"auto" \| "dialog" \| "sheet"` | `"auto"` | `auto` renders a centered dialog from the `sm` breakpoint (640px) up and a bottom sheet you can drag away below it. The server renders the dialog form and corrects it on hydration. A dialog that is closed at load shows nothing until it opens, so nothing shifts; one that is open at load (a route such as `?edit=1`) shows as the centered dialog on a phone until hydration swaps it to the sheet. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | The centered dialog's maximum width: `sm` 24rem, `md` 32rem, `lg` 40rem. |
| `title` (required) | `string` |  | Names the dialog for screen readers and shows at its top. Always required; hide it with `hideTitle`. |

## Modes

`DialogProps` is a union: pass the props of exactly one mode. Mixing them is a type error.

### Controlled: `DialogControlledProps`

A controlled Dialog: you own `open` and update it in `onOpenChange`.

| Prop | Type | Description |
| --- | --- | --- |
| `onOpenChange` (required) | `(open: boolean) => void` | Called with `false` when the user dismisses the dialog (close button, Escape, outside press, swipe). |
| `open` (required) | `boolean` | Whether the dialog is open. When a touch or pen tap opened it (on your own button, say), the centered dialog focuses itself rather than its first input, so no software keyboard pops up; after a key press it focuses the first control. |

Not in this mode (typed `never`):

- `trigger`: Not with `open`. Remove `open` and `onOpenChange` to let a trigger open the dialog.

### With trigger: `DialogWithTriggerProps`

An uncontrolled Dialog: `trigger` opens it.

| Prop | Type | Description |
| --- | --- | --- |
| `trigger` (required) | `ReactElement` | The element that opens the dialog, usually a Button: `trigger={<Button>Edit</Button>}`. |

Not in this mode (typed `never`):

- `onOpenChange`: Not with `trigger`. Remove `trigger` to control the dialog with `open` and `onOpenChange`.
- `open`: Not with `trigger`. Remove `trigger` to control the dialog with `open` and `onOpenChange`.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `isOpen`: Not a prop. Ouro UI uses `open` with `onOpenChange`: `<Dialog open={open} onOpenChange={setOpen}>`.
- `onClose`: Not a prop. `onOpenChange(false)` fires on close: `<Dialog open={open} onOpenChange={setOpen}>`.
- `position`: Not a prop. A Dialog is centered, or a bottom sheet on phones. For another edge use `<Sheet side="…">`.
- `side`: Not a prop. A Dialog is centered, or a bottom sheet on phones. For another edge use `<Sheet side="…">`.
- `snapPoints`: Not a prop. A Dialog fits its content. For snap points use `<Sheet snapPoints={[0.5, 1]}>`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Parts

When these props cannot express your layout, build it from the parts in `@/components/ouro/dialog/dialog-parts`. See [parts.md](parts.md#dialog-parts) for the required nesting.
