Skip to content

Components · Overlays

Dialog

Modal with focus trap, Escape and focus return.

@rookcom/monarch-reactComponent

Confirmation

Usage examples

Form with a busy save

Purpose

A modal window for a focused task or decision that must be finished or dismissed before returning to the page.

Use when

  • A short form that should not leave the page.
  • A focused decision with custom content.

Avoid when

  • A yes/no confirmation: use ConfirmDialog, which adds the alertdialog role, pending and error states.
  • Detail views or longer forms beside the page: use Sheet.
  • Phone-first flows that should rise from the bottom: use ResponsiveDialog.
  • Non-blocking supplementary content: use Popover.
  • Long or multi-step work: use a page.

Design principles

  • Every dialog has a DialogTitle (visually hidden if necessary) so it has an accessible name.
  • Header, body and footer are separate parts so long content scrolls while actions stay visible.
  • Dismissal is predictable: Escape and the close button always work unless an action is running (busy); outside clicks can be turned off for forms with typed values.
  • One Escape press closes one layer: a control whose own popup is open keeps the press (escapeGuard).

API reference

import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@rookcom/monarch-react';
ExportKindRenderingRoot class
Dialogcomponentclient.mn-overlay
DialogBodycomponentclient.mn-overlay
DialogClosecomponentclient.mn-overlay
DialogContentcomponentclient.mn-overlay
DialogDescriptioncomponentclient.mn-overlay
DialogFootercomponentclient.mn-overlay
DialogHeadercomponentclient.mn-overlay
DialogTitlecomponentclient.mn-overlay
DialogTriggercomponentclient.mn-overlay

Dialog, DialogTrigger and DialogClose are the Radix Dialog Root, Trigger and Close.

DialogContent renders in a portal with the overlay and an optional built-in close button.

footer is shorthand for a trailing <DialogFooter>; the parts still work on their own.

Generated from the TypeScript declarations of packages/react/src/components/overlays/dialog.

Properties

Dialog

Modal dialog. Focus is trapped inside, Escape closes it, and focus returns to the trigger. Every dialog needs a DialogTitle (visually hidden if necessary) so it has an accessible name.

PropTypeDescription
childrenReact.ReactNode

DialogBody

PropTypeDescription
childrenReactNode
classNamestring
idstring

DialogClose

PropTypeDescription
asChildboolean
childrenReactNode
classNamestring
disabledboolean
idstring

DialogContent

PropTypeDescription
asChildboolean
busybooleanAn action is in progress: Escape, outside clicks and the built-in close button no longer close the dialog, and the content is `aria-busy`. Disable your own Cancel button too.
childrenReactNode
classNamestring
closeLabelstring | nullAccessible label of the built-in close button. Pass null to hide the button.
closeOnOutsideClickbooleanWhether a click outside the dialog closes it. Default `true`; turn it off for forms with typed values so a stray click does not lose them. Escape and the close button still work.
escapeGuardbooleanIgnore Escape while focus is on a control whose own popup is open (a combobox or menu button with `aria-expanded="true"`), so that press only closes the popup. Default `true`.
footerReactNodeActions rendered in a DialogFooter after the children. The footer stays visible while DialogBody scrolls.
fullScreenOnPhonebooleanBelow 640px wide, fill the whole screen instead of floating with a margin.
idstring
size"sm" | "md" | "lg" | "xl"Width preset: 24rem, 32rem (default), 44rem or 60rem, capped at the viewport minus 2rem.

DialogDescription

PropTypeDescription
asChildboolean
childrenReactNode
classNamestring
idstring

DialogFooter

PropTypeDescription
childrenReactNode
classNamestring
idstring

DialogHeader

PropTypeDescription
childrenReactNode
classNamestring
idstring

DialogTitle

PropTypeDescription
asChildboolean
childrenReactNode
classNamestring
idstring

DialogTrigger

PropTypeDescription
asChildboolean
childrenReactNode
classNamestring
disabledboolean
idstring

Variants

PropValuesDescription
size (DialogContent)'sm''md''lg''xl'Width 24rem, 32rem (default), 44rem or 60rem, never wider than the viewport minus 2rem. Exposed as data-size.
fullScreenOnPhone (DialogContent)'false''true'Below 640px the dialog fills the screen with safe-area padding. Exposed as data-fullscreen-phone.

States

StateExposed asBehaviour
Open / closeddata-stateFrom Radix; content enters with the mn-dialog-in animation.
Without close button–closeLabel={null} removes the built-in close button.
Busydata-busy, aria-busybusy blocks Escape, outside clicks and the built-in close button while an action runs.

Interaction behaviour

  • Opening moves focus into the dialog; Tab and Shift+Tab stay inside it.
  • Escape, the built-in close button or any DialogClose closes it, and focus returns to the trigger.
  • A click on the overlay closes it, unless closeOnOutsideClick={false}.
  • While busy, nothing closes it; disable your own Cancel button as well.
  • Escape is ignored while focus is on a combobox or menu button whose popup is open (escapeGuard, on by default); Radix popups inside the dialog are already stacked layers.
  • Controlled with open + onOpenChange, or uncontrolled.

Keyboard support

KeysAction
Enter / SpaceOn the trigger, opens the dialog.
Tab / Shift+TabCycles focus within the dialog.
EscapeCloses the dialog and returns focus to the trigger.

Accessibility

Role
dialog
Accessible name
DialogTitle; DialogDescription becomes the accessible description.
  • The built-in close button is named by closeLabel (default "Close").
  • Content outside the dialog is hidden from assistive technology while open (Radix).
  • While busy, the content has aria-busy="true" and the close button is disabled.
  • For confirmations of destructive actions use ConfirmDialog, which sets role="alertdialog".

Responsive behaviour

  • Width is min(size, 100vw - 2rem) and height at most 100dvh - 2rem, so the dialog fits small screens.
  • With fullScreenOnPhone, below 640px it fills the screen and pads for notches and home bars (env(safe-area-inset-*)).
  • The body scrolls; header and footer stay put.

Theme support

  • Raised surface, border and shadow tokens; light, dark and more-contrast themes apply automatically.
  • The entrance animation uses duration tokens, which are 0ms under prefers-reduced-motion.

Do and don't

Do

  • Always include a DialogTitle.
  • Put the confirming action last in the footer.
  • Set busy while saving so a stray Escape cannot interrupt it.
  • Turn off outside clicks for forms with typed values.

Don't

  • Do not open a dialog from inside another dialog.
  • Do not hide the only way to close it.
  • Do not leave busy on after an error; let the person retry or cancel.
  • ConfirmDialog: Ready-made confirmation built on Dialog.
  • Sheet: Side or bottom panel for longer tasks.
  • ResponsiveDialog: This dialog on wide screens, a bottom sheet on phones.
  • Popover: Non-modal floating content.

Dependencies

KindDepends on
npm packages@radix-ui/react-dialogreact
Monarch componentsNone

Dismissal rules are shared with Sheet through src/internal/dismiss.ts.

Testing coverage

  • Opens, traps focus, closes on Escape and restores focus to the trigger.
  • Has an accessible name and description; built-in close button is present.
  • closeLabel={null} removes the close button.
  • No axe violations in the open dialog (jsdom).
  • footer renders a DialogFooter after the children.
  • busy blocks Escape, outside clicks and the close button, and sets aria-busy.
  • closeOnOutsideClick={false} keeps it open on an overlay click; Escape still closes.
  • Uncontrolled dialogs close on an overlay click by default.
  • Escape guard: Escape on an expanded combobox does not close it; escapeGuard={false} lets it through.
  • A consumer onEscapeKeyDown still runs and its preventDefault is honoured.
  • size and fullScreenOnPhone are exposed as data attributes.
  • Renders on the server (closed).
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: gallery/dialog and overlays/dialog-light (open); Cross-browser axe: /components/dialog

Tests: packages/react/src/components/overlays/dialog