# Modal

> Slug: `modal` · Category: Feedback

## When to use
- Confirming destructive or irreversible actions (delete account, cancel order).
- A focused task that takes the user out of the main flow but should return them — quick forms, choosers, terms acceptance.
- Surfacing critical info that must be acknowledged before proceeding.

## When NOT to use
- For non-blocking feedback — use `Snackbar`.
- For inline notices — use `NoticeBar`.
- For full-page error states — use `FullPageInterruption`.
- For sustained settings panels — prefer a side drawer or dedicated route.

## Variants & decision tree
`Modal` is controlled (`isOpen`). Configure actions via props — the action buttons are NOT children:

- **Confirmation** (most common) → set `headline`, `primaryActionLabel`, `secondaryActionLabel`. Body copy goes in `children`.
- **Single primary action** → set `headline` + `primaryActionLabel`. The component renders only one button.
- **Info-only / dismiss-only** → omit both action labels, set `closeAction={true}` (or leave it; if no `primaryActionLabel`, `closeAction` defaults to true) so users can close the dialog.
- **Sticky footer** → set `isButtonTraySticky` for long bodies that scroll.
- **Primary on right** → `primaryActionOnRight` flips button order for certain flows.

If `headline` is omitted, set `ariaLabel` so the dialog still has an accessible name.

## States
- `isOpen` toggles the open class.
- `inverse` swaps to dark-background styling.
- `focused` is for visual testing only.

## Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `isOpen` | `boolean` | `false` | Controls visibility. |
| `headline` | `string` | — | Title; also used for `aria-labelledby`. |
| `eyebrow` | `string` | — | Small text above the headline. |
| `children` | `ReactNode` | — | Body content. |
| `primaryActionLabel` | `string` | — | Primary button label. |
| `secondaryActionLabel` | `string` | — | Secondary button label. |
| `onPrimaryAction` | `() => void` | — | Primary click. |
| `onSecondaryAction` | `() => void` | — | Secondary click. |
| `onClose` | `() => void` | — | Close-button click. |
| `closeAction` | `boolean` | auto | Show the close icon. Auto-true when no `primaryActionLabel`. |
| `isButtonTraySticky` | `boolean` | `false` | Sticky footer. |
| `primaryActionOnRight` | `boolean` | `false` | Reverse button order. |
| `ariaLabel` | `string` | — | Required when `headline` is omitted. |
| `inverse` | `boolean` | inherited | Dark-background styling. |

## Usage examples
```tsx
const [open, setOpen] = useState(false);

<Button variant="primary" onClick={() => setOpen(true)}>Delete account</Button>

<Modal
  isOpen={open}
  headline="Delete account"
  eyebrow="Account"
  primaryActionLabel="Delete"
  secondaryActionLabel="Cancel"
  closeAction
  onPrimaryAction={handleDelete}
  onSecondaryAction={() => setOpen(false)}
  onClose={() => setOpen(false)}
>
  <p>This cannot be undone. All your data will be removed.</p>
</Modal>
```

## Composition
Body content goes in `children` — actions are configured via labels/handlers, NOT as children buttons. Pair with `ArrowThemeProvider` for theming.

## Accessibility
- Renders `<dialog role="dialog" aria-modal="true">` with `aria-labelledby` (headline) or `aria-label` (when no headline).
- Move focus into the dialog on open and trap focus there.
- Support Esc-to-close and outside-click-to-close (wire to `onClose`).
- Hide background content from screen readers (use `aria-hidden` on the page root if needed).

## Do / Don't
- Do configure actions via `primaryActionLabel` / `secondaryActionLabel`.
- Do return focus to the trigger when closed.
- Don't render `<Button>` children expecting them to appear in the tray.
- Don't open multiple modals at once.
