---
outline: deep
---

# Dialog

Dialogs display critical information or request user input in a modal overlay that blocks interaction with the rest of the page. Commonly used for confirmations, forms, and alerts.

**`<l-dialog>`** — Custom Element · Shadow DOM

## Options

### Basic

Open with `command="--show"` on a trigger button. Any descendant with `command="--hide"` closes the dialog. Note the `--` prefix — it's required by the native [Invoker Commands API](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API) for custom elements.

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="my-dialog"
>
  Open dialog
</button>

<l-dialog
  id="my-dialog"
  title="Dialog title"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="my-dialog"
  ></button>
  <article class="h-[340px]">…</article>
  <menu slot="footer">
    <button
      autofocus
      type="button"
      class="l-button"
      command="--hide"
      commandfor="my-dialog"
    >
      Cancel
    </button>
    <button
      type="button"
      class="l-button"
      data-variant="primary"
    >
      Confirm
    </button>
  </menu>
</l-dialog>
```

### Light dismiss

Add `light-dismiss` to close when the backdrop is clicked.

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="dialog-light-dismiss"
>
  Open dialog
</button>

<l-dialog
  id="dialog-light-dismiss"
  title="Dialog title"
  light-dismiss
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="dialog-light-dismiss"
  ></button>
  <article class="py-4">
    <p>Click the backdrop or press Escape to close.</p>
  </article>
  <menu slot="footer">
    <button
      type="button"
      class="l-button"
      command="--hide"
      commandfor="dialog-light-dismiss"
    >
      Close
    </button>
  </menu>
</l-dialog>
```

### Scrollable content

Long content can scroll while the header stays in view.

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="dialog-scrollable"
>
  Open dialog
</button>

<l-dialog
  id="dialog-scrollable"
  title="Terms of Service"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="dialog-scrollable"
  ></button>
  <article class="flex flex-col gap-4">
    <h3 class="font-semibold">1. Acceptance</h3>
    <p>
      By accessing or using this service, you agree to be bound by these terms. If you disagree with
      any part of the terms, you may not access the service. These terms apply to all visitors,
      users, and others who access or use the service.
    </p>
    <h3 class="font-semibold">2. Accounts</h3>
    <p>
      When you create an account with us, you must provide accurate, complete, and current
      information at all times. Failure to do so constitutes a breach of the terms, which may result
      in immediate termination of your account on our service.
    </p>
    <p>
      You are responsible for safeguarding the password that you use to access the service and for
      any activities or actions under your password, whether your password is with our service or a
      third-party service.
    </p>
    <h3 class="font-semibold">3. Content</h3>
    <p>
      Our service allows you to post, link, store, share and otherwise make available certain
      information, text, graphics, videos, or other material. You are responsible for the content
      that you post, including its legality, reliability, and appropriateness.
    </p>
    <p>
      By posting content, you grant us the right and license to use, modify, publicly perform,
      publicly display, reproduce, and distribute such content on and through the service. You
      retain any and all of your rights to any content you submit, post or display.
    </p>
    <h3 class="font-semibold">4. Prohibited uses</h3>
    <p>
      You may use the service only for lawful purposes and in accordance with these terms. You agree
      not to use the service in any way that violates any applicable national or international law
      or regulation.
    </p>
    <p>
      You may not exploit, harm, or attempt to exploit or harm minors in any way by exposing them to
      inappropriate content, asking for personally identifiable information, or otherwise.
    </p>
    <p>
      You may not transmit, or procure the sending of, any advertising or promotional material,
      including any junk mail, chain letter, spam, or any other similar solicitation.
    </p>
    <h3 class="font-semibold">5. Termination</h3>
    <p>
      We may terminate or suspend your account immediately, without prior notice or liability, for
      any reason whatsoever, including without limitation if you breach the terms. Upon termination,
      your right to use the service will immediately cease.
    </p>
    <p>
      If you wish to terminate your account, you may simply discontinue using the service. All
      provisions of the terms which by their nature should survive termination shall survive
      termination.
    </p>
    <h3 class="font-semibold">6. Limitation of liability</h3>
    <p>
      In no event shall the company, nor its directors, employees, partners, agents, suppliers, or
      affiliates, be liable for any indirect, incidental, special, consequential, or punitive
      damages resulting from your use of the service.
    </p>
    <h3 class="font-semibold">7. Changes</h3>
    <p>
      We reserve the right, at our sole discretion, to modify or replace these terms at any time. If
      a revision is material, we will provide at least 30 days notice prior to any new terms taking
      effect. What constitutes a material change will be determined at our sole discretion.
    </p>
  </article>
  <menu slot="footer">
    <button
      type="button"
      class="l-button"
      command="--hide"
      commandfor="dialog-scrollable"
    >
      Decline
    </button>
    <button
      type="button"
      class="l-button"
      data-variant="primary"
      command="--hide"
      commandfor="dialog-scrollable"
    >
      Accept
    </button>
  </menu>
</l-dialog>
```

### Blurred backdrop

Set `--backdrop-blur` to any CSS length to frost the page behind the dialog. Defaults to `0` (no blur).

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="dialog-blurred-backdrop"
>
  Open dialog
</button>

<l-dialog
  id="dialog-blurred-backdrop"
  title="Dialog title"
  style="--backdrop-blur: 4px"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="dialog-blurred-backdrop"
  ></button>
  <p>The page behind this dialog is blurred via <code>--backdrop-blur</code>.</p>
  <menu slot="footer">
    <button
      type="button"
      class="l-button"
      command="--hide"
      commandfor="dialog-blurred-backdrop"
    >
      Cancel
    </button>
    <button
      type="button"
      class="l-button"
      data-variant="primary"
      command="--hide"
      commandfor="dialog-blurred-backdrop"
    >
      Confirm
    </button>
  </menu>
</l-dialog>
```

### Form with autofocus

Add `autofocus` to any focusable element inside the dialog to focus it automatically on open.

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="dialog-form"
>
  Edit profile
</button>

<l-dialog
  id="dialog-form"
  title="Edit profile"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="dialog-form"
  ></button>
  <form class="flex flex-col gap-4">
    <label class="flex flex-col gap-1">
      <span class="text-sm font-medium">Name</span>
      <input
        autofocus
        type="text"
        name="name"
        value="Ada Lovelace"
        required
        class="rounded border px-3 py-2 text-sm outline-none focus:border-sky-500"
      />
    </label>
    <label class="flex flex-col gap-1">
      <span class="text-sm font-medium">Email</span>
      <input
        type="email"
        name="email"
        value="ada@example.com"
        required
        class="rounded border px-3 py-2 text-sm outline-none focus:border-sky-500"
      />
    </label>
  </form>
  <menu slot="footer">
    <button
      type="button"
      class="l-button"
      command="--hide"
      commandfor="dialog-form"
    >
      Cancel
    </button>
    <button
      type="button"
      class="l-button"
      data-variant="primary"
      command="--hide"
      commandfor="dialog-form"
    >
      Save
    </button>
  </menu>
</l-dialog>
```

### Without footer

Omit the `footer` slot and the footer row collapses — no empty space is reserved at the bottom.

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="dialog-without-footer"
>
  Open dialog
</button>

<l-dialog
  id="dialog-without-footer"
  title="Release notes"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="dialog-without-footer"
  ></button>
  <article class="h-[340px]">
    <p>
      Leave out the <code>footer</code> slot and the footer row collapses — no empty padded band at
      the bottom.
    </p>
  </article>
</l-dialog>
```

### Without header

Add `without-header` to drop the header row entirely (title and close slot). Useful for confirmation prompts where the body already carries the heading. Provide an accessible heading inside the body and rely on `Escape` or a footer action to close.

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="dialog-without-header"
>
  Delete account
</button>

<l-dialog
  id="dialog-without-header"
  without-header
>
  <div class="grid justify-items-center gap-3 pb-2 text-center">
    <span
      class="flex size-12 items-center justify-center rounded-full bg-red-100 text-red-700 dark:bg-red-950 dark:text-red-300"
    >
      <svg
        class="size-6"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        aria-hidden="true"
      >
        <path d="M12 9v4" />
        <path d="M12 17h.01" />
        <path
          d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
        />
      </svg>
    </span>
    <h2 class="text-lg font-semibold">Delete your account?</h2>
    <p class="text-sm">
      This action is permanent and cannot be undone. All your data, including projects and billing
      history, will be erased.
    </p>
  </div>
  <menu slot="footer">
    <button
      autofocus
      type="button"
      class="l-button"
      command="--hide"
      commandfor="dialog-without-header"
    >
      Cancel
    </button>
    <button
      type="button"
      class="l-button"
      data-variant="destructive"
      command="--hide"
      commandfor="dialog-without-header"
    >
      Delete account
    </button>
  </menu>
</l-dialog>
```

## Accessibility

### Criteria

- **Role** — Rendered as a native `<dialog>` in the shadow root — built-in `dialog` role and modal semantics
- **Accessible name** — The `title` property renders as an `<h2>` in the header, or provide a custom heading via `slot="title"`
- **Focus management** — Focus is trapped inside the modal; moves to the first focusable element on open
- **Focus restoration** — Focus returns to the trigger element when the dialog closes
- **Close button** — Consumer provides the close button via `slot="close"` with `aria-label="Close"`
- **Motion** — Respects `prefers-reduced-motion`

### Rules

- Always set a meaningful `title` — it becomes the dialog heading and accessible name
- Put the close button in `slot="close"` with `class="l-close"` and `command="--hide"` `commandfor="<id>"`
- Use `command="--show"` and `command="--hide"` with `commandfor` pointing at the dialog id. The `--` prefix is mandatory for custom elements

### Keyboard interactions

- `Escape` — Closes the dialog
- `Tab` — Cycles focus through focusable elements inside the dialog
- `Shift + Tab` — Cycles focus backward through focusable elements inside the dialog

## API reference

### Importing

```js
import 'luxen-ui/dialog';
```

### Attributes & Properties

- **title**: `string` — Dialog title rendered in the header and used as the dialog's accessible name.
- **open**: `boolean` (default: `false`) — Whether the dialog is open.
- **light-dismiss**: `boolean` (default: `false`) — Close when the backdrop is clicked.
- **without-header**: `boolean` (default: `false`) — Hide the header entirely (title and close slot).

### Commands

Open and close the dialog by toggling its `open` property, or via the [Invoker Commands API](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API) from any light-DOM button. Custom commands must start with `--`.

>
> The Invoker Commands API is [✓ Baseline Newly Available (since 2025-12-12)](https://web-platform-dx.github.io/web-features-explorer/features/invoker-commands/). For older browser versions, load the [`invokers-polyfill`](https://npmx.dev/package/invokers-polyfill) once at app startup:
> 
> ```js
> import 'invokers-polyfill';
> ```
> 

- `--show` — Sets `open = true`.
- `--hide` — Sets `open = false`.

### Events

- **show** (cancelable) — Fired when the dialog is about to open. Cancelable — call `event.preventDefault()` to keep it closed.
- **after-show** — Fired after the open animation completes. Not cancelable.
- **hide** (cancelable) — Fired when the dialog is about to close. Cancelable — call `event.preventDefault()` to keep it open.
- **after-hide** — Fired after the close animation completes. Not cancelable.

### Slots

- **(default)** — Body content.
- **title** — Custom heading element. Overrides the default `<h2>` rendered from the `title` property. Also provides the dialog's accessible name.
- **close** — Close button (typically `<button class="l-close">`).
- **footer** — Footer actions.

### CSS parts

- `dialog` — The native `<dialog>` element.
- `header` — The header wrapper containing the title and close slot.
- `title` — The dialog title heading.
- `body` — The body wrapper around the default slot.
- `footer` — The footer wrapper around the footer slot.

### CSS custom properties

- `--width` (default: `31rem`) — Dialog width.
- `--border-radius` (default: `6px`) — Dialog border radius.
- `--padding` (default: `1.5rem`) — Padding applied to the header, footer, and inline-padding of the body. Set to `0` to remove all internal spacing (e.g. for edge-to-edge media).
- `--show-duration` (default: `200ms`) — Open transition duration.
- `--hide-duration` (default: `200ms`) — Close transition duration.
- `--backdrop` — Backdrop color.
- `--backdrop-blur` (default: `0`) — Backdrop blur amount (any CSS length). `0` means no blur; set e.g. `4px` for a subtle frost.
