> For the complete documentation index, see [llms.txt](https://analog.groupthought.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://analog.groupthought.com/reference/alpine/dialog.md).

# x-dialog

Directives and a magic for dialogs built on the native \<dialog> element: modal and non-modal, animated close, and focus restore.

Directives and a magic for dialogs built on the native `<dialog>` element: modal and non-modal, animated close, and focus restore.

| Directive / magic              | What it does                                                                                                              |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `x-dialog`                     | The root. Holds the open state for the child directives                                                                   |
| `x-dialog.modal`               | Opens with `showModal()`: focus trap, `::backdrop`, Escape closes                                                         |
| `x-dialog="{ isOpen: false }"` | Options object, or a bare boolean. `isOpen` accepts a get/set pair                                                        |
| `x-dialog:dialog`              | The `<dialog>` element itself. Keeps a server-rendered `id` when one exists                                               |
| `x-dialog:content`             | The content wrapper inside the `<dialog>`. A click outside it (on the backdrop) closes the dialog                         |
| `x-dialog:button`              | Opens on click (`aria-controls`, `aria-expanded`, `aria-haspopup="dialog"`)                                               |
| `x-dialog:close`               | Closes on click                                                                                                           |
| `x-dialog:backdrop`            | A custom backdrop for non-modal dialogs, since `::backdrop` only exists for modal ones. Shown while open, closes on click |
| `$dialog`                      | The state, readable anywhere inside the root: `isOpen`, `wasOpen`, `open()`, `close()`, `waitForAnimations()`             |

The dialog uses the button for its accessible name through `aria-labelledby`. Escape closes a modal dialog and native `showModal()` traps focus. On close, the plugin waits for CSS animations before calling `close()`, then returns focus to the element that opened it. If that element has been hidden or replaced, focus moves to the closest equivalent instead of `<body>`.

## Examples

### A basic dialog

Use `x-dialog:button` and `x-dialog:close` to open and close a `<dialog>`:

```html
<div x-dialog>
  <button x-dialog:button>Open</button>
  <dialog x-dialog:dialog x-cloak>
    <div x-dialog:content>
      Hello
      <button x-dialog:close>Close</button>
    </div>
  </dialog>
</div>
```

A click outside `x-dialog:content` closes the dialog, so keep it tight around the visible panel.

### A modal dialog

Use the `modal` modifier to block the rest of the page while the dialog is open:

```html
<div x-dialog.modal x-scroll-lock="$dialog.isOpen">
  <button x-dialog:button>Play video</button>
  <dialog x-dialog:dialog x-cloak class="backdrop:bg-canvas/80 backdrop:backdrop-blur-md">
    …
  </dialog>
</div>
```

`.modal` opens with `showModal()`, so the browser traps focus, renders the `::backdrop`, and closes on Escape. Add `x-scroll-lock="$dialog.isOpen"` to stop the page behind it from scrolling. Analog drawers and video popups use both.

### Styling the close animation

Use `$dialog.wasOpen` to style the dialog on its way out:

```html
<dialog
  x-dialog:dialog
  :data-closing="!$dialog.isOpen && $dialog.wasOpen"
  class="animate-drawer from-left"
>
  …
</dialog>
```

The plugin keeps the `<dialog>` open until its CSS animations finish. `wasOpen` separates "closing" from "never opened". The `drawer` and `dialog` snippets use this `data-closing` attribute for their exit animation.

### Driving the dialog from outside

Use a get/set pair for `isOpen` to bind the dialog to your own state:

```html
<div
  x-dialog.modal="{
    get isOpen() { return $store.drawer.isOpen && $store.drawer.type === 'cart' },
    set isOpen(value) {
      $dispatch(value ? 'theme:cart-drawer:open' : 'theme:cart-drawer:close')
    }
  }"
>
  …
</div>
```

The cart and header drawers bind to the global drawer store this way, so any button on the page can open them by dispatching an event. Anything inside the root can also call `$dialog.open()` and `$dialog.close()` directly.

### Starting open

Use a server-rendered `open` attribute on the `<dialog>` to start the dialog open:

```html
<div x-dialog>
  <dialog x-dialog:dialog open>…</dialog>
</div>
```

The plugin reads the attribute on init and syncs `$dialog.isOpen` to it. Passing `isOpen: true` in the options does the same from state.

## Options

| Option   | What it does                                                                             |
| -------- | ---------------------------------------------------------------------------------------- |
| `isOpen` | Whether the dialog is open (default `false`). Accepts a get/set pair for two-way binding |

In Analog, render the `dialog` or `drawer` snippet inside an `x-dialog` root. The snippets include the positioning, animation, and close button. For a smaller overlay that floats next to its trigger, see [x-popup](/reference/alpine/popup.md).
