Skip to content

Alert Dialog

A modal that overlays the page and traps focus within a confirmation flow. It's semantically distinct from a regular dialog — use it for destructive actions, critical confirmations, or any flow that demands explicit user acknowledgment before proceeding.

import { AlertDialog } from "@areia/slots";

document.querySelector("#root")!.innerHTML = `
  <div data-slot="alert-dialog">
    <button data-slot="alert-dialog-trigger" class="win95-button">
      Delete project
    </button>
    <div data-slot="alert-dialog-portal">
      <div
        data-slot="alert-dialog-overlay"
        class="fixed inset-0 bg-black/40"
      ></div>
      <div
        data-slot="alert-dialog-content"
        class="win95-window fixed left-1/2 top-1/2 w-96 -translate-x-1/2 -translate-y-1/2"
      >
        <div data-slot="alert-dialog-header">
          <h2 data-slot="alert-dialog-title" class="win95-titlebar">
            Delete project?
          </h2>
          <p data-slot="alert-dialog-description" class="mb-3">
            This action cannot be undone. The project and all related data will be
            permanently deleted.
          </p>
        </div>
        <div data-slot="alert-dialog-footer" class="flex justify-end gap-2">
          <button data-slot="alert-dialog-cancel" class="win95-button">
            Cancel
          </button>
          <button data-slot="alert-dialog-action" class="win95-button">
            Delete
          </button>
        </div>
      </div>
    </div>
  </div>
`;

const root = document.querySelector(
  '[data-slot="alert-dialog"]',
)!;
const controller = AlertDialog.createAlertDialog(root, {
  closeOnEscape: true,
  lockScroll: true,
});
controller.open();

Import

import { AlertDialog } from "@areia/slots";

Usage

import { AlertDialog } from "@areia/slots";

// Auto-bind a single root
const root = document.querySelector(
  '[data-slot="alert-dialog"]',
)!;
const dialog = AlertDialog.createAlertDialog(root, {
  lockScroll: true,
});

// Listen for state changes
root.addEventListener("alert-dialog:change", (e) => {
  console.log(e.detail.open);
});

// Open imperatively from application code
dialog.open();

// Auto-bind all unbound roots in scope
const controllers = AlertDialog.create();

Expected Markup

The alert dialog requires both alert-dialog-overlay and alert-dialog-content slots. Optional structural slots (alert-dialog-header, alert-dialog-media, alert-dialog-footer) help organize the layout. Cancel and action buttons are wired through click delegation on their respective data-slot attributes.

<div data-slot="alert-dialog">
  <button data-slot="alert-dialog-trigger">
    Delete project
  </button>

  <div data-slot="alert-dialog-portal">
    <div data-slot="alert-dialog-overlay"></div>

    <div data-slot="alert-dialog-content">
      <div data-slot="alert-dialog-header">
        <div data-slot="alert-dialog-media">!</div>
        <h2 data-slot="alert-dialog-title">Delete project?</h2>
        <p data-slot="alert-dialog-description">
          This action cannot be undone. The project and all
          related data will be permanently deleted.
        </p>
      </div>

      <div data-slot="alert-dialog-footer">
        <button data-slot="alert-dialog-cancel">Cancel</button>
        <button data-slot="alert-dialog-action">Delete</button>
      </div>
    </div>
  </div>
</div>

Data Slots

SlotRequiredDescription
alert-dialogYesRoot container.
alert-dialog-triggerNoElement that opens the dialog on click.
alert-dialog-portalNoContainer moved to document.body when open. Without it, overlay/content stay in place.
alert-dialog-overlayYesBackdrop behind the content. Throws if missing.
alert-dialog-contentYesThe dialog panel itself. Throws if missing.
alert-dialog-titleNoHeading element linked via aria-labelledby.
alert-dialog-descriptionNoBody text linked via aria-describedby.
alert-dialog-headerNoGroups media, title, and description.
alert-dialog-mediaNoIcon or illustration at the top of the header.
alert-dialog-footerNoGroups action buttons.
alert-dialog-cancelNoButton that closes the dialog when clicked. Click is delegated on content.
alert-dialog-actionNoButton for the primary action (does NOT close the dialog automatically).

Generated Attributes

AttributeElement(s)Description
data-stateRoot, portal, overlay, content"open" or "closed".
data-openRoot, portal, overlay, contentPresent when the dialog is open.
data-closedRoot, portal, overlay, contentPresent when the dialog is closed.
data-stack-indexalert-dialog-contentStack order when multiple modals are open.
rolealert-dialog-content ("alertdialog"), alert-dialog-overlay ("presentation")ARIA roles.

Events

Outbound

EventDetailDescription
alert-dialog:change{ open: boolean }Fired on the root when the open state changes.

Inbound

EventDetailDescription
alert-dialog:set{ open: boolean }Dispatch on the root to open or close the dialog.

API Reference

Options

OptionTypeDefaultDescription
defaultOpenbooleanfalseInitial open state.
onOpenChange(open: boolean) => voidCalled when the open state changes.
onPortalMounted(container: HTMLElement) => voidAfter alert-dialog-portal mounts to document.body on open.
closeOnClickOutsidebooleanfalseClose when clicking the overlay. Unlike a regular dialog, this defaults to false.
closeOnEscapebooleantrueClose when pressing Escape.
lockScrollbooleantrueLock body scroll while the dialog is open.

Controller

MemberTypeDescription
open()() => voidOpen the alert dialog.
close()() => voidClose the alert dialog.
toggle()() => voidToggle between open and closed.
isOpenboolean (getter)Current open state.
destroy()() => voidRemove all listeners, restore scroll, and clean up portal.

Controller

The controller is returned by AlertDialog.createAlertDialog() and provides imperative open/close control.

const dialog = AlertDialog.createAlertDialog(root, {
  lockScroll: true,
});

// Open programmatically
dialog.open();

// Check if open
console.log(dialog.isOpen); // true

// Close programmatically
dialog.close();

// Toggle
dialog.toggle();

// Clean up
dialog.destroy();

Cancel and Action Buttons

The controller delegates click handling on the content slot:

  • Clicking any element with data-slot="alert-dialog-cancel" inside the content closes the dialog.
  • Clicking any element with data-slot="alert-dialog-action" does not close the dialog — the application is responsible for handling the action and calling dialog.close() when done.

Focus Management

On open, focus moves to the first focusable element inside the content (respecting autofocus). Tab is trapped within the content. On close, focus returns to the previously active element or the trigger.

Multiple alert dialogs (and regular dialogs) share a modal stack. The top-most dialog receives focus, and data-stack-index reflects the z-order so that overlays and panels can be styled accordingly.

[data-slot="alert-dialog-overlay"] {
  z-index: calc(
    50 + var(--alert-dialog-overlay-stack-index, 0)
  );
}