Kanzo UI
Overlays & feedback

Dialog

A modal surface that interrupts the flow to ask for input or confirmation.

Ark UI

Usage

import {
  Dialog,
  DialogBody,
  DialogClose,
  DialogContent,
  DialogFooter,
  DialogHeader,
  DialogTrigger,
} from "@kanzo-tech/ui";
<Dialog>
  <DialogTrigger asChild>
    <Button variant="outline">Claim contract</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader title="Something is eating the bell-ropes" description="Q-1041 · Thornmarch" />
    <DialogBody>…</DialogBody>
  </DialogContent>
</Dialog>

DialogContent renders its own Portal, overlay and positioner, so a page never has to assemble them. It is also lazyMount + unmountOnExit by default — the body is not in the tree at all until the dialog opens, which keeps forms inside it from mounting on page load.

Anatomy

Dialog
├── DialogTrigger
└── DialogContent          (portal + overlay + positioner)
    ├── DialogHeader
    │   ├── DialogTitle
    │   └── DialogDescription
    ├── DialogBody
    ├── DialogFooter
    │   └── DialogClose
    └── DialogClose        (the built-in X)

DialogHeader accepts title and description as props and renders the title/description sub-components for you, so the common case is one line. Pass children instead when the header needs more than text.

The three the tree names in a parenthesis are exported too. DialogContent renders them, so you only reach for them to rearrange the floating layer: DialogPositioner, the box the content is placed in, and DialogOverlay, the scrim behind it. Ark's Portal is the third.

Sizes

Controlled

onOpenChange receives a details object, not a boolean — read details.open.

open: false

Three surfaces, one machine

Dialog, Sheet and AlertDialog are the same Ark machine wearing three shapes, which is why they share a header, a body and a title:

What it isReach for it when
Dialoga centred modalthe task is short and self-contained
Sheetthe same modal docked to an edgethe panel wants the full height or width — a filter rail, a detail pane
AlertDialogthe same modal as role="alertdialog", with no close buttondismissing by reflex would be the wrong outcome

A Sheet has no SheetTitle and an AlertDialog has no separate title either: both render DialogTitle, because one machine has one title.

Alert mode

AlertDialog is Dialog with role="alertdialog" and showCloseButton={false}. The X is removed on purpose, so the only way out is an explicit Cancel or Confirm — reach for it when dismissing by reflex would be the wrong outcome, and plain Dialog for everything else.

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogClose,
  AlertDialogContent,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTrigger,
} from "@kanzo-tech/ui";
<AlertDialog>
  <AlertDialogTrigger asChild>
    <Button variant="outline">Abandon</Button>
  </AlertDialogTrigger>
  <AlertDialogContent size="sm">
    <AlertDialogHeader title="Abandon Q-1058?" description="The party is recalled and it is marked Failed." />
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogClose asChild>
        <AlertDialogAction variant="destructive">Abandon</AlertDialogAction>
      </AlertDialogClose>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

The parts mirror their Dialog counterparts one-to-one, with two additions in the footer:

AlertDialogFooter
├── AlertDialogCancel     (outline Button + close trigger)
└── AlertDialogAction     (default or destructive Button)

AlertDialogBody is exported alongside them for prose longer than the header's description — it is DialogBody with one rule added, dropping its top padding when an AlertDialogHeader is already above it, so the two do not stack their spacing into a gap.

AlertDialogCancel already wraps itself in a close trigger. AlertDialogAction does not — it is a plain Button, because the confirm path usually runs an async handler and decides for itself when to close; wrap it in AlertDialogClose asChild when it should close immediately.

API Reference

dialogContentVariants and dialogOverlayVariants are exported for a floating layer you assemble yourself — the size scale and the scrim, so a hand-built dialog stays the same box as this one.

Dialog

PropTypeDefault
modalbooleantrue
lazyMountbooleantrue
unmountOnExitbooleantrue

Extends React.ComponentProps<typeof ArkDialog.Root>. modal={false} also suppresses DialogOverlay, so a non-modal dialog does not dim the page behind it.

DialogContent

PropTypeDefault
size"sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "fullscreen""md"
bottomStickOnMobilebooleantrue
showCloseButtonbooleantrue

bottomStickOnMobile turns the dialog into a bottom sheet below the sm breakpoint — square top corners, full width, slide-up entrance — which is where a centred modal reads worst.

The close button is absolutely positioned, so DialogTitle adds inline-end padding whenever a close trigger is present in the content. That is why a long title wraps early instead of running under the X — and why setting showCloseButton={false} gives the padding back.

DialogHeader

PropTypeDefault
titlestring
descriptionstring

DialogBody

PropTypeDefault
scrollFadebooleanfalse

The body is wrapped in a ScrollArea, so long content scrolls inside the dialog while the header and footer stay pinned. scrollFade adds the mask at the scroll edges.

AlertDialogAction

PropTypeDefault
variant"default" | "destructive""default"

Narrowed from the full Button scale — a confirmation only ever affirms or destroys; every other Button prop still passes through. The remaining AlertDialog* parts take the same props as their Dialog counterparts, minus showCloseButton, which is forced off.

On this page