# Dialog

> A decision that needs the whole screen: the page fades back under a veil of its own colour and the panel rises out of a blur.

Most dialogs drop a black overlay over the page and lock scroll by restyling <html>, which shifts the layout. Here the page fades back under a veil of its own background with no blur, the panel rises out of a 4px blur, and scrolling is stopped at the dialog instead. It is the native <dialog>, so focus is trapped and the page is inert without extra code.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/dialog.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: none
- Hairline UI components it needs (the CLI installs them too): `ui-interaction-motion`
- Docs and live preview: https://hairlineui.com/ui/dialog
- Registry item (source): https://hairlineui.com/r/dialog.json

## Usage

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

<Dialog
  trigger={<button>Delete project</button>}
  title="Delete Northwind?"
  description="Its 14 pages and every deploy go with it."
  footer={({ close }) => <><button onClick={close}>Cancel</button><button onClick={remove}>Delete</button></>}
/>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open / defaultOpen / onOpenChange` | `boolean / (open) => void` | – | State. |
| `trigger` | `ReactElement` | – | A button that opens it. |
| `title / description` | `ReactNode` | – | Heading and the line under it (aria-labelledby / describedby). |
| `children / footer` | `ReactNode \| ({ close }) => ReactNode` | – | Body and buttons. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 400, 520 or 680px wide. |
| `dismissible` | `boolean` | `true` | A press on the veil closes it. |

## States

- **open**: Veil fades in (260ms); the panel rises 8px from 0.96 out of a 4px blur (420ms).
- **close**: Escape, ✕ or the veil; it sinks back (220ms) and focus returns.
- **long content**: The body scrolls inside the panel; the page stays still.

## What it handles

Scroll locks that set overflow on <html> reflow the page and jump the scrollbar. This stops wheel and touch scrolling at the dialog, so nothing behind it moves.

- **Native dialog**: showModal(): top layer, inert page, focus trap, Escape.
- **No layout shift**: The page's own styles are never touched.
- **Focus returns**: To whatever opened it, after the exit.
- **Bottom on phones**: It sits at the bottom of small screens, within thumb reach.

## Replaces

Radix Dialog, focus-trap, scroll lock, portal

## Where it fits

- **Destructive confirmations**: Delete a project, with the reason and a red action.
- **Short forms**: Invite a teammate or rename a workspace.
- **Terms and details**: Long content that scrolls inside the panel.
- **Upgrade prompts**: A plan choice before a paid action.

## Notes

- Built on the native <dialog> (modal.ts, shared with the other modal surface): focus stays inside, the page is inert, Escape works, and it sits in the top layer.
- Reduced motion: it fades.
- Buttons stack full-width on phones, primary last.

License: MIT (https://hairlineui.com/license#components)
