Components / Dialog

Dialog

A centered modal for focused content and small forms: renaming a post, editing settings, picking options. Blocking yes/no confirmations use AlertDialog instead.

import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@layers/ui/dialog'

Rendered stock

House rules

  • Triggers attach via render={<Button />}; there is no asChild in the Base UI wrappers.
  • DialogHeader, body content, and DialogFooter are direct children of DialogContent, which spaces them with a built-in grid gap. A form wraps only its fields; the submit button lives in the footer, linked via form={id}.
  • DialogContent owns ALL popup padding (p-6) and rhythm (gap-6) — stock maia, nothing bespoke. Body wrappers never carry padding utilities: a p-6 body div double-pads the dialog to 48px edges. Inner surfaces inside the body (a bg-secondary well, a Card) pad themselves; the body wrapper itself stays layout-only (grid/space-y/gap).
  • The body is a STACK OF CARDS, one per subject (founder ruling 2026-08-06): the form in one Card, a teardown in another, an explanation in a third. One subject still means one card — that is the point, not an edge case. It is a forcing function: a dialog ships with one job and bare fields read fine, then the next person adds a second job and, with no grouping already in place, the two dissolve into one column. Confirmations (AlertDialog) and dialogs whose whole body is a sentence stay uncarded, having nothing to group.
  • Footer buttons are size="lg" (40px) and right-aligned; Cancel is the outline button, first, primary action last. A Cancel/dismiss is always present except single-acknowledgement dialogs whose lone button already dismisses ("Done"). xl/48px is for full-width auth CTAs, not footers.
  • A dialog taller than the viewport scrolls; it never overflows. DialogContent caps at calc(100dvh-4rem) and scrolls as one by default — stock shadcn sets no cap, which left tall dialogs unreachable at both ends (header above the top edge, footer below the bottom, nothing to scroll). When the header and footer should stay in view instead, put DialogContent in scrollable mode and wrap the middle in DialogBody: the popup becomes a flex column and only the body scrolls. Never hand-roll max-h-[60vh] overflow-y-auto on a body div — the correct height is whatever the popup has left after its header and footer, which only DialogBody expresses.
  • Command inside a Dialog uses CommandInline, never raw Command.
  • Nested dialogs keep their backdrop: Base UI suppresses backdrops of dialogs opened from inside another dialog or sheet, so every house overlay sets forceRender (a modal always dims what is behind it). If a dialog ever opens undimmed, it is using a raw Base UI Backdrop instead of the house wrapper.
  • Never use stock Sheet for panel surfaces; inspectors are WindowSheet and canvas detail views are CanvasSheet.

See also