Askr
UI & Components

Dialog

Use dialog with the current published Askr packages.

  • @askrjs/ui/dialog0.0.13
  • @askrjs/themes/dialog0.0.13

How to use dialog

Dialog should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep dialog labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Button, Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from '@askrjs/themes/components';

<Dialog>
  <DialogTrigger asChild><Button>Edit project</Button></DialogTrigger>
  <DialogContent>
    <DialogTitle>Edit project</DialogTitle>
    <DialogDescription>Changes are visible immediately.</DialogDescription>
    <ProjectForm />
  </DialogContent>
</Dialog>

Purpose

Trapping focus and blocking interaction with everything behind it is what Dialog does at its core, forcing the user to either act or dismiss before continuing. Reach for it when a task needs undivided attention — a form, a confirmation step, a detail view — and letting the user ignore it and keep scrolling would be the wrong call. When the content is purely informational and doesn't need to block the rest of the page, Popover is the better fit.

Install and import

Pull the behavior from `@askrjs/ui/dialog` and the default look from `@askrjs/themes/dialog`; both subpaths are real exports in their respective packages. If you're already importing from the package roots, `Dialog` and its parts are also re-exported from `@askrjs/ui` and `@askrjs/themes/components` directly — pick whichever import style keeps your bundle graph easiest to reason about.

Live examples

A minimal dialog is `Dialog` wrapping a `DialogTrigger` and a `DialogPortal` containing `DialogOverlay` plus `DialogContent`. `DialogContent` is where `DialogTitle`, `DialogDescription`, and any form fields or `DialogClose` buttons live. Toggle `open`/`onOpenChange` yourself for a controlled dialog, or leave it uncontrolled with `defaultOpen` and let the trigger and close buttons manage state internally.

Anatomy

The full tree is `Dialog` > `DialogTrigger` (the element that opens it) and `DialogPortal` > `DialogOverlay` (the dimmed backdrop) and `DialogContent` (the actual panel), with `DialogTitle`, `DialogDescription`, and `DialogClose` nested inside the content. Every piece is a separate component you compose yourself rather than a single component with variant props, so you can drop in your own markup between them.

State model

A dialog's open state is just a boolean, driven by `open`, `defaultOpen`, and `onOpenChange` on the root `Dialog` component. `modal` controls whether interaction with the rest of the page is blocked while it's open — leave it on unless you have a specific reason to allow background interaction. There's no separate loading or error state baked in; anything beyond open/closed is left to whatever you render inside `DialogContent`.

Keyboard and accessibility

`DialogContent` accepts `role` set to `dialog` or `alertdialog`, and wires up `aria-modal`, `aria-labelledby` from `DialogTitle`, and `aria-describedby` from `DialogDescription` automatically. Escape closes the dialog through `onEscapeKeyDown`, and clicking outside the content fires `onPointerDownOutside`/`onInteractOutside`; both are overridable if you need to intercept the close, and `onDismiss` fires as the common exit hook regardless of which path triggered it. Focus moves into the content on open and returns to the trigger on close.

Styling and tokens

The default theme targets the dialog by its `data-slot` values — `dialog-overlay` and `dialog-content` — plus `data-state` for open/closed transitions, so you can override appearance with plain CSS selectors instead of prop soup. The overlay backdrop uses `--ak-color-backdrop` with a blur and sits at `--ak-z-modal-backdrop`, while the content surface sits above it at `--ak-z-modal` and reads `--ak-color-bg` and `--ak-color-border` for its surface and border. Open and close transitions animate opacity and scale using the shared `--ak-duration-fast` and `--ak-ease-standard` tokens.

API

`Dialog` takes `open`, `defaultOpen`, `onOpenChange`, and `modal`. `DialogContent` adds `forceMount`, `role`, `onEscapeKeyDown`, `onPointerDownOutside`, `onInteractOutside`, and `onDismiss` on top of the usual div props, and `DialogOverlay` only adds `forceMount`. `DialogTrigger` and `DialogClose` are button-like components (they accept `onPress` and `disabled` rather than raw `onClick`), and every part supports `asChild` if you want to render your own element instead of the library's default tag.

Edge cases

Set `forceMount` on `DialogOverlay` or `DialogContent` when you need the nodes present in the DOM even while closed — useful for measuring layout or driving your own exit animation instead of relying on `data-state`. Nesting dialogs works, but each one manages its own focus trap and portal, so test the stacking order and z-index carefully if you go there. If you intercept `onPointerDownOutside` or `onEscapeKeyDown` to block dismissal (say, during a save), make sure you still give the user some other way out.

Alert Dialog reuses this exact component tree under the hood for the case where you specifically need a confirm/cancel action pair. Drawer and Sheet are also Dialog under a different name in the themed layer, just with slide-in positioning instead of a centered panel. If what you actually want is a non-modal floating panel, see Popover.