ComponentsOverlays & feedback

Dialog

A focused task that keeps the user in context — new contact, insert link, leave request. Fixed header and footer with a scrolling body, five sizes, a close guard for unsaved work, and a CSS-only bottom sheet below 640px.

Radix Dialog components/dialog.tsx, styles/overlays.csstheme · material · accent · density · font

Installation

$ pnpm add @vamos-ui/react

The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";

Usage

import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogBody, DialogFooter, DialogClose, Button } from "@vamos-ui/react";

<Dialog>
  <DialogTrigger asChild><Button>New contact</Button></DialogTrigger>
  <DialogContent size="md">
    <DialogHeader title="New contact" description="Add a contact manually." />
    <DialogBody>…</DialogBody>
    <DialogFooter>
      <DialogClose asChild><Button>Cancel</Button></DialogClose>
      <Button variant="primary">Save</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Title is required for accessibility. Use srOnly when it shouldn't be visible.

Glass budget: the overlay blurs the page (--overlay-blur), so the panel drops its own blur and turns slightly more opaque. Nested layers never blur.

Close order on mobile sheets: footer buttons stack with the primary on top.

Examples

Sizes

sm 400 · md 520 · lg 680 · xl 880 px · full (viewport minus a 12px gutter on each side).

Long content

Only DialogBody scrolls; the header and footer stay put, and a hairline appears under the header once you scroll.

Unsaved changes (HR)

onBeforeClose runs on every close attempt — Esc, overlay, × and DialogClose. Return a confirm() promise to ask first.

Submitting and error

While saving: dismissible={false}, buttons locked, the primary shows loading. Errors go in an inline Alert at the top of the body.

Mobile behaviour

Below 640px: mobile="sheet" (default) anchors to the bottom, "center" stays a card, "fullscreen" is for long forms. CSS only — no component swap.

Narrow your browser below 640px, then open each one.

Menus inside a dialog

Nested layers get data-layer-depth 2 and never blur; in glass mode only the overlay does.

API

Dialog

PropTypeDefault
open
Controlled open state.
boolean—
defaultOpen
Initial state when uncontrolled.
booleanfalse
onOpenChange
Called after a change is allowed (close guards run first).
(open: boolean) => void—
modal
false keeps the page interactive: no overlay, no focus trap.
booleantrue

DialogContent

PropTypeDefault
size
Maximum width: 400 / 520 / 680 / 880 px or the viewport minus 24px. Height follows content up to min(85dvh, --dialog-max-h).
"sm" | "md" | "lg" | "xl" | "full""md"
mobile
Layout below 640px.
"sheet" | "center" | "fullscreen""sheet"
dismissible
false → Esc and overlay clicks don't close; the × button and DialogClose still work.
booleantrue
onBeforeClose
Runs on every close attempt. Return or resolve false to stay open.
() => boolean | Promise<boolean>—
hideClose
Hide the built-in × button.
booleanfalse
container
Portal target. Defaults to the nearest ThemeScope / PortalContainer, else body.
HTMLElement | null—
labels
Built-in strings (dictionary namespace "dialog").
{ close?: string }—
overlayClassName
Class of the overlay element.
string—
onOpenAutoFocus
Initial focus goes to [data-autofocus], else the first focusable. preventDefault to handle it yourself.
(event: Event) => void—
…
onEscapeKeyDown, onPointerDownOutside, onInteractOutside, forceMount…
Radix Dialog.Content props—

DialogHeader (LayerHeader)

PropTypeDefault
title
Required. Becomes the accessible name (aria-labelledby).
ReactNode—
description
Linked with aria-describedby; omitted cleanly when absent.
ReactNode—
icon
Leading icon or icon well.
ReactNode—
action
Trailing element next to the title (badge, menu).
ReactNode—
srOnly
Keep the title for screen readers only.
booleanfalse

DialogBody (LayerBody)

PropTypeDefault
flush
Remove the padding (full-bleed lists, media).
booleanfalse

DialogFooter (LayerFooter)

PropTypeDefault
align
end groups buttons at the end; between pushes the first child to the start.
"end" | "between" | "stretch""end"

Other exports

PropTypeDefault
DialogTrigger / DialogClose
Use asChild with a Button.
Radix parts—
DialogOverlay
The backdrop (DialogContent renders its own). blurred prop controls the glass blur.
component—
DialogTitle / DialogDescription
For fully custom headers.
Radix parts—
useDialogRoot()
Close from custom code; the guard still runs.
() => { open, requestOpenChange, modal }—