Askr documentation
UI & Components

Dialog

Dialog: anatomy, keyboard behavior, state, and theming in Askr.

Example

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>
Try Dialog

Open and dismiss a modal while focus stays managed.

Published props

Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.

DialogCloseAsChildProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
disableddisabled?: boolean | undefined;——
onPressonPress?: ((event: PressEvent) => void) | undefined;——
refref?: Ref<Element>;——
typetype?: undefined;——

DialogCloseProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
disableddisabled?: boolean | undefined;——
onPressonPress?: ((event: PressEvent) => void) | undefined;——
refref?: Ref<HTMLButtonElement>;——
typetype?: "button" | "submit" | "reset" | undefined;——

DialogContentAsChildProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
forceMountforceMount?: boolean | undefined;——
onDismissonDismiss?: (() => void) | undefined;——
onEscapeKeyDownonEscapeKeyDown?: ((event: KeyboardEvent) => void) | undefined;——
onInteractOutsideonInteractOutside?: ((event: Event) => void) | undefined;——
onPointerDownOutsideonPointerDownOutside?: ((event: PointerEvent) => void) | undefined;——
refref?: Ref<Element>;——
restoreFocusrestoreFocus?: HTMLElement | (() => HTMLElement | null) | null | undefined;—Explicit focus target used when a controlled dialog has no persistent trigger.
rolerole?: "dialog" | "alertdialog" | undefined;——

DialogContentOwnProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
forceMountforceMount?: boolean | undefined;——
onDismissonDismiss?: (() => void) | undefined;——
onEscapeKeyDownonEscapeKeyDown?: ((event: KeyboardEvent) => void) | undefined;——
onInteractOutsideonInteractOutside?: ((event: Event) => void) | undefined;——
onPointerDownOutsideonPointerDownOutside?: ((event: PointerEvent) => void) | undefined;——
restoreFocusrestoreFocus?: HTMLElement | (() => HTMLElement | null) | null | undefined;—Explicit focus target used when a controlled dialog has no persistent trigger.
rolerole?: "dialog" | "alertdialog" | undefined;——

DialogContentProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
forceMountforceMount?: boolean | undefined;——
onDismissonDismiss?: (() => void) | undefined;——
onEscapeKeyDownonEscapeKeyDown?: ((event: KeyboardEvent) => void) | undefined;——
onInteractOutsideonInteractOutside?: ((event: Event) => void) | undefined;——
onPointerDownOutsideonPointerDownOutside?: ((event: PointerEvent) => void) | undefined;——
refref?: Ref<HTMLDivElement>;——
restoreFocusrestoreFocus?: HTMLElement | (() => HTMLElement | null) | null | undefined;—Explicit focus target used when a controlled dialog has no persistent trigger.
rolerole?: "dialog" | "alertdialog" | undefined;——

DialogDescriptionAsChildProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
refref?: Ref<Element>;——

DialogDescriptionProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
refref?: Ref<HTMLParagraphElement>;——

DialogOverlayAsChildProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
forceMountforceMount?: boolean | undefined;——
refref?: Ref<Element>;——

DialogOverlayOwnProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
forceMountforceMount?: boolean | undefined;——

DialogOverlayProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
forceMountforceMount?: boolean | undefined;——
refref?: Ref<HTMLDivElement>;——

DialogOwnProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
childrenchildren?: unknown;——
defaultOpendefaultOpen?: boolean | undefined;——
idid?: string | undefined;——
modalmodal?: boolean | undefined;——
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;——
openopen?: boolean | undefined;——

DialogPortalProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
childrenchildren?: unknown;——

DialogProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
childrenchildren?: unknown;——
defaultOpendefaultOpen?: boolean | undefined;——
idid?: string | undefined;——
modalmodal?: boolean | undefined;——
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;——
openopen?: boolean | undefined;——

DialogTitleAsChildProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
refref?: Ref<Element>;——

DialogTitleProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
refref?: Ref<HTMLHeadingElement>;——

DialogTriggerAsChildProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
disableddisabled?: boolean | undefined;——
onPressonPress?: ((event: PressEvent) => void) | undefined;——
refref?: Ref<Element>;——
typetype?: undefined;——

DialogTriggerProps

Import from @askrjs/themes/dialog.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
disableddisabled?: boolean | undefined;——
onPressonPress?: ((event: PressEvent) => void) | undefined;——
refref?: Ref<HTMLButtonElement>;——
typetype?: "button" | "submit" | "reset" | undefined;——

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-surface-raised (not the plain --ak-color-bg token — dialogs specifically use the raised-surface variant) for its background. The open and close transitions aren't symmetric: opening scales in over --ak-duration-normal, while closing scales out faster, over --ak-duration-fast — both share the same --ak-ease-standard easing, just different durations.

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 Dialog behavior for confirmation flows. Sheet adds themed edge positioning while retaining the Dialog state, focus and dismissal model. Use Popover for a floating panel with its own interaction model.