Dialog

A modal window for a focused task. Focus is trapped while it is open and returned to the trigger when it closes.

Import

tsx
import { Dialog } from "@merid/react";

Anatomy

tsx
<Dialog.Root>
  <Dialog.Trigger />
  <Dialog.Content>
    <Dialog.Title />
    <Dialog.Description />
    <Dialog.Footer>
      <Dialog.Close>Cancel</Dialog.Close>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog.Root>
  • Root holds the open state. It renders no element.
  • Trigger is a <button> that toggles the dialog. Pass asChild to render your own element, such as a Button, instead.
  • Content is the modal surface, portalled to document.body with a backdrop. size sets its maximum width.
  • Title labels the dialog (aria-labelledby). Always include one.
  • Description is linked with aria-describedby when present.
  • Footer lays out actions, right-aligned.
  • Close with text children renders a secondary Button; use asChild to supply a different element, such as a primary Button. Content renders the top-right icon close automatically (showClose, on by default); a Close with no children renders that icon button yourself instead, and the automatic one steps aside.
  • Popovers, menus, selects and tooltips opened inside Content layer above the dialog, so a Select in a dialog form just works.

Examples

Sizes

size sets the maximum width: sm 440px, md 560px (default), lg 720px, full the viewport minus a 16px margin. At 640px and below every size becomes a bottom sheet.

Controlled

Pass open and onOpenChange to keep the state in your component, for example to open the dialog from somewhere other than a Trigger.

Initial focus

Focus moves to the element passed as initialFocus, then to an element with data-autofocus, then to the first tabbable element.

tsx
<Dialog.Content>
  <Dialog.Title>Rename</Dialog.Title>
  <Input data-autofocus defaultValue="Northwind" />
</Dialog.Content>

Blocking dismissal

Set closeOnOutsidePress={false} or closeOnEscape={false} when losing unsaved work would be costly. Prefer AlertDialog for confirmations.

API reference

Dialog.Root

PropTypeDefaultDescription
openboolean–Controlled open state.
defaultOpenbooleanfalseInitial open state when uncontrolled.
onOpenChange(open: boolean) => void–Called when the open state should change.
childrenReactNode–Trigger, Content and anything else sharing this dialog's state.

Dialog.Trigger

Accepts every <button> attribute. type defaults to "button" (not set when asChild is used).

PropTypeDefaultDescription
asChildbooleanfalseRender the single child element (e.g. your own `Button`) instead of a `<button>`, merging props, ref and handlers.
refRef<HTMLButtonElement>–Forwarded ref to the button.

Dialog.Content

Accepts every <div> attribute except role.

PropTypeDefaultDescription
sizeDialogSize"md"Maximum width: `sm` 440px, `md` 560px, `lg` 720px, `full` the viewport minus a 16px margin. `DialogSize` is `"sm" | "md" | "lg" | "full"`.
closeOnOutsidePressbooleantrueClose when the backdrop is pressed.
closeOnEscapebooleantrueClose on Escape.
initialFocusRefObject<HTMLElement | null>–Element to focus when opened; defaults to [data-autofocus], then the first tabbable element.
containerElement | nulldocument.bodyPortal target. `undefined` uses `document.body`; `null` renders nothing until the target exists.
showClosebooleantrueRender the standard top-right icon close button automatically. Skipped while you render your own icon `Close`.
closeLabelstring"Close"Accessible name of the automatic close button.
refRef<HTMLDivElement>–Forwarded ref to the dialog element.

Dialog.Title

Renders an <h2>. Accepts every heading attribute.

PropTypeDefaultDescription
refRef<HTMLHeadingElement>–Forwarded ref to the heading.

Dialog.Description

Renders a <p>. Accepts every paragraph attribute.

PropTypeDefaultDescription
refRef<HTMLParagraphElement>–Forwarded ref to the paragraph.

Dialog.Close

Accepts every <button> attribute.

PropTypeDefaultDescription
iconbooleantrue without childrenRender the standard top-right icon button. When false (default if children are given) it renders the children as a secondary Button.
asChildbooleanfalseRender the single child element (e.g. your own `Button`) instead of a `<button>`, merging props, ref and handlers.
refRef<HTMLButtonElement>–Forwarded ref to the button.

Renders a <div> and accepts every <div> attribute; it has no additional props.

Styling

ClassElement
.mrd-dialog__backdropFixed backdrop, var(--mrd-backdrop)
.mrd-dialogThe surface: max width from data-size (440 / 560 / 720px / full), --mrd-radius-card, --mrd-shadow-2xl
.mrd-dialog__titleHeading
.mrd-dialog__descriptionSupporting text
.mrd-dialog__footerAction row
.mrd-dialog__closeIcon close button

Content carries data-size; content and backdrop carry data-state="open" while mounted; the trigger carries data-state="open" | "closed". At 640px and below the dialog becomes a bottom sheet and the footer stacks its actions. Tokens used: --mrd-backdrop, --mrd-surface, --mrd-z-overlay, --mrd-duration-enter. Popovers, menus, selects and tooltips opened inside the dialog layer above it (--mrd-z-popover, --mrd-z-tooltip). A Close with text children renders as .mrd-button with data-variant="secondary". To style the Trigger, or a Close as another variant, pass asChild and a Button.

Accessibility

Follows the WAI-ARIA Dialog (Modal) pattern. Content has role="dialog" and aria-modal="true"; the Trigger has aria-haspopup="dialog" and aria-expanded. Page scroll is locked while open.

KeyAction
SpaceEnterOn the trigger, opens the dialog.
TabMoves focus to the next tabbable element inside the dialog, wrapping at the end.
ShiftTabMoves focus to the previous tabbable element, wrapping at the start.
EscCloses the dialog and returns focus to the trigger.

Guidelines

Do

Use a dialog for a short, focused task the person asked for, such as editing one record.

Avoid

Open a dialog unprompted, or nest a dialog inside another dialog.

Do

Name the primary action with a verb that says what happens: Save, Invite, Rename.

Avoid

Use vague labels like OK or Yes for the primary action.
  • AlertDialog for confirmations that need a response.
  • Drawer for longer content alongside the page.
  • Popover for non-modal, anchored content.