Dialog

A modal window that overlays the page and traps focus until dismissed, from @clerk/headless. It is a headless primitive: it supplies open state, portalling, an overlay surface, a centering viewport with optional body scroll lock, focus management, dismissal (outside press / Escape), and ARIA wiring, but ships no styles — you bring your own CSS by targeting the data-* state attributes each part emits.

Example

The demo below is intentionally unstyled — it renders the raw primitive so you can see its behavior and ARIA wiring. Open it, then press Escape or click outside the popup to dismiss.

Usage

import { Dialog } from '@clerk/headless/dialog';

<Dialog.Root>
  <Dialog.Trigger>Open</Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Backdrop />
    <Dialog.Viewport>
      <Dialog.Popup>
        <Dialog.Title>Confirm action</Dialog.Title>
        <Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
        <Dialog.Close>Cancel</Dialog.Close>
      </Dialog.Popup>
    </Dialog.Viewport>
  </Dialog.Portal>
</Dialog.Root>;

Controlled

const [open, setOpen] = useState(false);

<Dialog.Root
  open={open}
  onOpenChange={setOpen}
>
  {/* trigger + portal */}
</Dialog.Root>;

Non-modal (page stays interactive)

<Dialog.Root modal={false}>{/* focus is not trapped and the rest of the page stays interactive */}</Dialog.Root>

Parts

PartDefault ElementDescription
Dialog.Rootnone (context)Owns open state, ARIA ids, modal mode, and the transition lifecycle
Dialog.Trigger<button>Toggles the dialog open on click
Dialog.Portalnone (portal)Portals its children; renders nothing until mounted
Dialog.Backdrop<div>Semi-transparent overlay surface behind the popup
Dialog.Viewport<div>Fixed centering container; owns body scroll lock
Dialog.Popup<div>The dialog content container (role="dialog", focus-trapped)
Dialog.Title<h2>Heading; wired to the popup's aria-labelledby
Dialog.Description<p>Description; wired to the popup's aria-describedby
Dialog.Close<button>Closes the dialog on click

All rendered parts accept a render prop for polymorphic rendering and standard HTML attributes for their default element. Compound parts throw if used outside Dialog.Root. Dialog.Title and Dialog.Description manage their own id. Dialog.Portal is optional; for centered, scroll-locked modal behavior nest Dialog.Popup inside Dialog.Viewport.

Props

Dialog.Root

PropTypeDefaultDescription
openbooleanControlled open state
defaultOpenbooleanfalseInitial open state (uncontrolled)
onOpenChange(open: boolean) => voidCalled when the open state changes
modalbooleantrueTrap focus and make the rest of the page inert

Dialog.Portal

PropTypeDefaultDescription
rootHTMLElement | null | RefObject<HTMLElement>document.bodyContainer element to portal into

Dialog.Viewport

PropTypeDefaultDescription
lockScrollbooleantrueLock body scroll while the dialog is open

Dialog.Trigger, Dialog.Backdrop, Dialog.Popup, Dialog.Title, Dialog.Description, and Dialog.Close take no additional props beyond standard HTML attributes for their default element.

Styling

The headless parts are unstyled. Target a part with your own class (or render prop) and combine it with the data-* state attributes each part emits:

AttributeApplies ToDescription
data-openTrigger, Backdrop, Viewport, PopupPresent when the dialog is open
data-closedTrigger, Backdrop, Viewport, PopupPresent when closed (still mounted while exiting)
data-starting-styleBackdrop, Viewport, PopupPresent on the entering frame
data-ending-styleBackdrop, Viewport, PopupPresent during the exit animation

The backdrop, viewport, and popup stay mounted through the exit animation, so enter/exit transitions are CSS-driven:

.dialog-popup {
  opacity: 1;
  transition: opacity 150ms ease;
}
.dialog-popup[data-starting-style],
.dialog-popup[data-ending-style] {
  opacity: 0;
}