Dialog

A headless, accessible modal dialog with focus trapping, keyboard dismissal, and composable parts.

Features

  • Focus trapping
  • Focus restoration
  • Escape key dismissal
  • Controlled or uncontrolled open state
  • Accessible title and description wiring
  • Headless styling

Installation

npm install @ariaui/dialog

Examples

Modal dialog with focus trapping and Escape to dismiss. Compose Root, Trigger, Overlay, Content, and related parts. The sample wraps Overlay and Content in Portal.Root so the layer stacks above the page—style with className using your design tokens.

Edit profile dialog

Portal-backed modal dialog with title, description, form fields, and action buttons.

Preview

No preview output yet.

live.tsxReady

Framer Motion dialog

Dialog overlay and content animated with Framer Motion through asChild composition.

Preview

No preview output yet.

live.tsxReady

Anatomy

tsx
import * as Dialog from "@ariaui/dialog";

export default function Example() {
  return (
    <Dialog.Root>
      <Dialog.Trigger />
      <Dialog.Portal>
        <Dialog.Overlay />
        <Dialog.Content>
          <Dialog.Title />
          <Dialog.Description />
          <Dialog.Cancel />
          <Dialog.Action />
          <Dialog.Close />
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

API Reference

Root

State container. Owns the open/closed state and provides context to all child components.

PropTypeDefault
open
boolean
defaultOpen
booleanfalse
onOpenChange
(open: boolean) => void
onClose
() => void

Trigger

Button that toggles the dialog open state.

AttributeValues
aria-haspopupdialog
aria-expanded"true" when open, "false" when closed
aria-controlsID of the Content element
[data-state]"open" | "closed"

Portal

Portal container for dialog layers. Renders children into document.body in the browser and inline during SSR.

Overlay

Background overlay rendered behind the dialog content. Typically styled with a semi-transparent background.

PropTypeDefault
asChild
booleanfalse
AttributeValues
[data-state]"open" | "closed"

Content

Dialog content container. Traps focus within the dialog, handles Escape dismissal, and restores focus on close.

PropTypeDefault
asChild
booleanfalse
AttributeValues
roledialog
aria-modaltrue
aria-labelledbyID of the Title element
aria-describedbyID of the Description element
[data-state]"open" | "closed"

Title

Accessible title for the dialog. Referenced by Content via aria-labelledby.

Description

Accessible description for the dialog. Referenced by Content via aria-describedby.

Close

Button that closes the dialog when clicked. Use for auxiliary close actions (e.g., X icon).

Cancel

Cancel button that closes the dialog when clicked. Use for secondary/destructive cancel actions.

AttributeValues
data-dialog-cancelPresent when rendered

Action

Action button that calls the onClick handler, then closes the dialog (unless preventDefault is called). Use for primary confirmation actions.

AttributeValues
data-dialog-actionPresent when rendered

Keyboard

ShortcutAction
EscClose the dialog and return focus to the trigger.
TabMove focus to the next focusable element within the dialog (loops to first).
Shift+TabMove focus to the previous focusable element within the dialog (loops to last).
Enter/SpaceActivate the focused button (trigger, close, or any action button).

Accessibility

The Dialog component implements the WAI-ARIA Dialog (Modal) pattern:

  • Content renders with role="dialog" and aria-modal="true" to mark the surface as modal.
  • Content is labelled via aria-labelledby pointing to the Title and aria-describedby pointing to the Description.
  • Trigger exposes aria-haspopup="dialog", aria-expanded, and aria-controls for assistive technology.
  • Focus is trapped within the dialog using FocusScope — Tab loops through focusable elements.
  • Escape closes the dialog from any focusable element inside it.
  • Focus is restored to the trigger element when the dialog closes.
Previous
Datepicker