# Modal

A dialog that opens on top of the page and blocks it until closed. Use it for focused tasks and confirmations; for supporting content next to the trigger, use [Popover](/components/popover.md).

```tsx
// demo.tsx
import {CloseIcon} from '@earendil-works/elanor-icons-react';
import {Button, IconButton, Modal, Stack} from '@earendil-works/elanor-react';

export default function ModalDemo() {
  return (
    <Modal.Root>
      <Modal.Trigger render={<Button hierarchy="secondary" />}>Rename project</Modal.Trigger>
      <Modal.Portal>
        <Modal.Backdrop />
        <Modal.Viewport>
          <Modal.Popup>
            <Stack direction="row" align="start" gap="auto">
              <Modal.Title>Rename project</Modal.Title>
              <Modal.Close render={<IconButton aria-label="Close" />}>
                <CloseIcon />
              </Modal.Close>
            </Stack>
            <Modal.Description>Members see the new name right away.</Modal.Description>
            <Modal.Body>The project URL stays the same, so existing links keep working.</Modal.Body>
            <Modal.Actions>
              <Modal.Close render={<Button />}>Save</Modal.Close>
              <Modal.Close render={<Button hierarchy="secondary" />}>Cancel</Modal.Close>
            </Modal.Actions>
          </Modal.Popup>
        </Modal.Viewport>
      </Modal.Portal>
    </Modal.Root>
  );
}
```

## Usage

```tsx
import {Modal} from '@earendil-works/elanor-react';

<Modal.Root>
  <Modal.Trigger />
  <Modal.Portal>
    <Modal.Backdrop />
    <Modal.Viewport>
      <Modal.Popup>
        <Modal.Title />
        <Modal.Description />
        <Modal.Body />
        <Modal.Actions>
          <Modal.Close />
        </Modal.Actions>
      </Modal.Popup>
    </Modal.Viewport>
  </Modal.Portal>
</Modal.Root>;
```

The modal moves focus inside, keeps it there, and returns it to the trigger on close. Escape and clicking the backdrop close it.

`Modal.Close` closes the modal; render it as a [Button](/components/button.md) or an [Icon button](/components/icon-button.md). Title and Description name and describe the dialog for screen readers.

## Static rendering

On pages rendered on the server without React in the browser, use the static parts. `Body` and `Actions` stay the same. Bind the behavior once the page has loaded:

```tsx
<Modal.StaticRoot>
  <Modal.StaticTrigger />
  <Modal.StaticPopup>
    <Modal.StaticTitle />
    <Modal.StaticDescription />
    <Modal.Body />
    <Modal.Actions>
      <Modal.StaticClose />
    </Modal.Actions>
  </Modal.StaticPopup>
</Modal.StaticRoot>
```

```ts
import {createModal, enhance} from '@earendil-works/elanor-react';

enhance({modal: createModal});
```

The popup is a native modal dialog: the browser traps focus, makes the page inert, and returns focus when it closes. Its buttons open and close it through invoker commands, also without the behavior. The behavior animates opening and closing like the React parts and closes it on Escape and on a press on the backdrop. To close it from a script, call the dialog's `requestClose()`, which closes it the same way as Escape. Give `StaticRoot` an `id` to open the dialog from other buttons too, with `commandfor` and `command="show-modal"`.

## API

### `Modal.Root`

Accepts Base UI [`Dialog.Root`](https://base-ui.com/react/components/dialog.md) props.

### `Modal.Trigger`

Accepts Base UI [`Dialog.Trigger`](https://base-ui.com/react/components/dialog.md) props.

### `Modal.Portal`

Accepts Base UI [`Dialog.Portal`](https://base-ui.com/react/components/dialog.md) props.

### `Modal.Backdrop`

Accepts Base UI [`Dialog.Backdrop`](https://base-ui.com/react/components/dialog.md) props.

### `Modal.Viewport`

Accepts Base UI [`Dialog.Viewport`](https://base-ui.com/react/components/dialog.md) props.

### `Modal.Popup`

Accepts Base UI [`Dialog.Popup`](https://base-ui.com/react/components/dialog.md) props.

### `Modal.Title`

Accepts Base UI [`Dialog.Title`](https://base-ui.com/react/components/dialog.md) props.

### `Modal.Description`

Accepts Base UI [`Dialog.Description`](https://base-ui.com/react/components/dialog.md) props.

### `Modal.Body`

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `Modal.Actions`

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `Modal.Close`

Accepts Base UI [`Dialog.Close`](https://base-ui.com/react/components/dialog.md) props.

### `Modal.StaticRoot`

A modal for pages without React in the browser; `createModal` makes it work. Renders no element.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | - | The dialog's id, so other buttons can open it with `commandfor`. Generated by default. |
| `children` | `ReactNode` | - |  |

Also accepts HTML attributes and `ref`.

### `Modal.StaticTrigger`

Renders `<button>`. Accepts its HTML attributes and `ref`.

### `Modal.StaticPopup`

The dialog, centered over the page's backdrop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data-elanor` | `string` | - | The page's own behaviors for this element, attached after the component's. |

Renders `<dialog>`. Also accepts its HTML attributes and `ref`.

### `Modal.StaticTitle`

Renders `<h2>`. Accepts its HTML attributes and `ref`.

### `Modal.StaticDescription`

Renders `<p>`. Accepts its HTML attributes and `ref`.

### `Modal.StaticClose`

Renders `<button>`. Accepts its HTML attributes and `ref`.
