---
title: Dialog
description: Dialogs interrupt the page to capture a decision or short form. They trap focus and return it when dismissed.
---

import { meta as DialogMeta, Alert, Default } from '@/content/stories/dialog';
import Canvas from '@/components/component-docs/canvas';
import Demo from '@/components/component-docs/demo';

export const config = {
	dependencies: {
		components: [Canvas, Demo],
		scripts: [
			'../../../components/component-docs/demo.script.tsx',
			'../../../components/component-docs/canvas.script.tsx',
			'../../../components/component-docs/controls.script.tsx',
		],
	},
};


# Dialog

<p class="docs-lede">Dialogs interrupt the page to capture a decision or short form. They trap focus and return it when dismissed.</p>

## Try it

<Demo of={Default} meta={DialogMeta} />

## Usage

Register dialogs with `installDialogs`, then open them imperatively via `openDialog` or a trigger with `data-dialog-open`. Compose title, body, and actions with the sub-components.

```tsx
import {
  RuiDialog,
  RuiDialogTitle,
  RuiDialogBody,
  RuiDialogActions,
  RuiDialogClose,
  installDialogs,
  openDialog,
} from '@ecopages/radiant-ui/dialog';

installDialogs();

<RuiDialog id="edit-profile" label="Edit profile">
  <RuiDialogTitle>Edit profile</RuiDialogTitle>
  <RuiDialogBody>Update your display name and email.</RuiDialogBody>
  <RuiDialogActions>
    <RuiDialogClose>Cancel</RuiDialogClose>
    <button type="button">Save</button>
  </RuiDialogActions>
</RuiDialog>
```

## Custom markup

`<rui-dialog>` coordinates any light-DOM tree that matches its query contract. The
`Rui*` helpers stamp these targets; they are not required.

```tsx
import '@ecopages/radiant-ui/dialog';

<rui-dialog open label="Edit profile">
  <div data-ref="root" class="rui-dialog">
    <div data-ref="backdrop" class="rui-dialog__backdrop"></div>
    <div data-ref="dialog" class="rui-dialog__surface" role="dialog" aria-modal="true" tabindex={-1}>
      <button type="button" data-dialog-close class="rui-dialog__close"></button>
      <div data-dialog-title class="rui-dialog__title">Edit profile</div>
      <div data-dialog-body class="rui-dialog__body">Update your display name and email.</div>
    </div>
  </div>
</rui-dialog>;
```

BEM classes are presentation-only. Set `alert` on the host for `role="alertdialog"` on `[data-ref="dialog"]`.

## Alert dialogs

<p>Set `alert` to `true` for destructive confirmations. Alert dialogs limit tab order to the controls inside `RuiDialogActions`, and toggling `alert` updates the role and the tab loop live. `RuiDialogClose` is labelled `Close` only when it renders the icon; with children, the text is its name.</p>

<Canvas of={Alert} meta={DialogMeta} />

## Return focus on close

<p>Focus returns to the element that opened the dialog. Avoid stacking multiple modal layers.</p>

## Theming

Dialog surfaces map to **semantic surface roles**, never Tailwind palette steps. Override `--rui-dialog-*` on `rui-dialog`.

| Part | Default | Override |
| --- | --- | --- |
| Backdrop | `overlay` scrim | theme `--color-overlay` |
| Surface fill / radius / shadow | `--background`, `--radius-container`, `--shadow-modal` | `--rui-dialog-surface`, `--rui-dialog-radius`, `--rui-dialog-shadow` |
| Surface padding / max width | `--space-inset`, `28rem` | `--rui-dialog-padding`, `--rui-dialog-max-width` |
| Narrow viewport inset | `1rem` | `--rui-dialog-inset-x` |
| Title | `on-background` | theme |
| Close button | `on-surface` | theme |

```css
rui-dialog {
  --rui-dialog-max-width: 40rem;
  --rui-dialog-padding: var(--space-6);
}
```

## Accessibility

- Every dialog needs an accessible name via `label` or `RuiDialogTitle`.
- Use `alert` for confirmations that require an explicit decision before continuing.
- Do not open a dialog without a clear way to dismiss it; provide a close action.

## API

`RuiDialog` is a custom element (`<rui-dialog>`) with a composition-first surface. Pass `title` / `actions` for the composite API, or compose the view helpers as children.

### Attributes (`<rui-dialog>`)

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | `false` | Whether the dialog is open. |
| `alert` | `boolean` | `false` | Uses `role="alertdialog"` for workflow-interrupting confirmations. |
| `label` | `string` | `''` | Accessible name when there is no visible title. |

### Light-DOM contract

| Target | Required | Host writes | Author owns |
| --- | --- | --- | --- |
| `[data-ref="root"]` | yes | `hidden` when closed | the wrapper node |
| `[data-ref="backdrop"]` | yes | — | the scrim node; click dismisses |
| `[data-ref="dialog"]` | yes | `aria-labelledby`, `aria-label`, `aria-describedby` | `role`, `aria-modal`, `tabIndex` (view seeds these) |
| `[data-dialog-title]` | no | `id`, `aria-labelledby` wiring | visible title text |
| `[data-dialog-body]` | no | `id`, `aria-describedby` wiring | body content |
| `[data-dialog-close]` | no | — | dismiss control; click emits `rui-close` |

Do not set `hidden` on `[data-ref="root"]` or `aria-*` on `[data-ref="dialog"]`. Nested hosts: none.

### Events

| Event | Detail | Description |
| --- | --- | --- |
| `rui-close` | `{ reason: 'escape' \| 'backdrop' \| 'dismiss' }` | Emitted when the dialog is dismissed. |

### View helpers

| Component | Target stamped | Notes |
| --- | --- | --- |
| `RuiDialog` | `DialogShell` targets | Pass `title` / `actions` for composite API, or compose helpers as children. |
| `RuiDialogTitle` | `[data-dialog-title]`, `data-ref="title"` | Visible accessible name. |
| `RuiDialogBody` | `[data-dialog-body]`, `data-ref="description"` | `aria-describedby` target. |
| `RuiDialogActions` | — | Action row; not queried by the host. |
| `RuiDialogClose` | `[data-dialog-close]`, `data-ref="close"` | Emits `rui-close` with reason `dismiss`. |

### CSS classes

Public BEM classes (documented via `@cssclass`):

| Class | Description |
| --- | --- |
| `.rui-dialog` | Root; hidden until `open`. |
| `.rui-dialog__backdrop` | Scrim using the `overlay` role. |
| `.rui-dialog__surface` | Modal panel: `background` + `rounded-container` + `shadow-modal`. |
| `.rui-dialog__title` | Dialog title; `aria-labelledby` target. |
| `.rui-dialog__body` | Dialog body; `aria-describedby` target. |
| `.rui-dialog__actions` | Right-aligned action row. |
| `.rui-dialog__close` | Dismiss button. |

### Theme roles

| Part | CSS variables consumed |
| --- | --- |
| Backdrop | `--overlay` |
| Surface | `--background`, `--on-background`, `--shadow-modal`, `--radius-container`, `--p-inset` |
| Close button | `--on-surface` |
