Marble UI
← Components

Dialog / Modal

Preview: Form

Modal dialog: overlay + modal + dialog in one. Put it inside a DialogTrigger, after the trigger button.

Import

import { Dialog, DialogHeader, DialogBody, DialogFooter, AlertDialog, DialogTrigger } from '@marbleui/ui'

Props

<Dialog>

PropTypeDefaultDescription
children *requiredReactNode—
isDismissablebooleantrue
overlayPropsOmit<ModalOverlayProps, 'children'>—
size'sm' | 'md' | 'lg''md'

Also accepts React Aria props (81).

<DialogHeader>

PropTypeDefaultDescription
title *requiredReactNode—
classNamestring—
descriptionReactNode—

<DialogBody>

PropTypeDefaultDescription
children *requiredReactNode—
classNamestring—
idstring—

<DialogFooter>

PropTypeDefaultDescription
children *requiredReactNode—
classNamestring—

<AlertDialog>

Confirmation dialog for destructive or important actions (role="alertdialog"). The body describes the dialog (aria-describedby). Focus starts on the action, or on Cancel when `destructive`, so Enter can't confirm by accident.

PropTypeDefaultDescription
actionLabel *requiredstring—
children *requiredReactNode—
title *requiredReactNode—
cancelLabelstring'Cancel'
destructiveboolean—
onAction() => void—

<DialogTrigger>

Re-exported from React Aria DialogTrigger; see its documentation for props.

Accessibility

Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria Dialog · React Aria DialogTrigger · React Aria Heading · React Aria Modal · React Aria ModalOverlay. Follow the primitive's docs for the full keyboard map.

Usage notes

Dialog bundles overlay, modal and dialog. Put it inside a DialogTrigger, after the element that opens it; focus moves in on open, is trapped while open, and returns to the trigger on close.

<DialogTrigger>
  <Button>New project</Button>
  <Dialog>
    {({ close }) => (
      <>
        <DialogHeader title="Create project" description="Projects group issues and docs." />
        <DialogBody>…</DialogBody>
        <DialogFooter><Button variant="outline" onPress={close}>Cancel</Button><Button onPress={close}>Create</Button></DialogFooter>
      </>
    )}
  </Dialog>
</DialogTrigger>
  • DialogHeader’s title becomes the dialog’s accessible name; always give one.
  • Use AlertDialog for confirmations that need an answer (not dismissable by clicking outside); add destructive for delete-style actions.
  • For edge panels use Sheet (no gestures) or Drawer (swipeable, snap points).
Example source · show
import { Button } from '../button'
import { Select, SelectItem } from '../select'
import { TextField } from '../text-field'
import { AlertDialog, Dialog, DialogBody, DialogFooter, DialogHeader, DialogTrigger } from './dialog'

export const styles = {
  Form: () => (
    <DialogTrigger>
      <Button>New project</Button>
      <Dialog>
        {({ close }) => (
          <>
            <DialogHeader title="Create project" description="Projects group issues, docs and members." />
            <DialogBody className="flex flex-col gap-4">
              <TextField label="Name" placeholder="Website redesign" autoFocus />
              <Select label="Team" defaultSelectedKey="design">
                <SelectItem id="design">Design</SelectItem>
                <SelectItem id="eng">Engineering</SelectItem>
                <SelectItem id="growth">Growth</SelectItem>
              </Select>
            </DialogBody>
            <DialogFooter>
              <Button variant="outline" onPress={close}>Cancel</Button>
              <Button onPress={close}>Create</Button>
            </DialogFooter>
          </>
        )}
      </Dialog>
    </DialogTrigger>
  ),
  Alert: () => (
    <DialogTrigger>
      <Button variant="danger">Delete workspace</Button>
      <AlertDialog title="Delete workspace?" actionLabel="Delete" destructive>
        This permanently removes 12 projects and 340 issues. This can't be undone.
      </AlertDialog>
    </DialogTrigger>
  ),
  Content: () => (
    <DialogTrigger>
      <Button variant="outline">Terms of service</Button>
      <Dialog size="lg">
        {({ close }) => (
          <>
            <DialogHeader title="Terms of service" description="Last updated October 2026" />
            <DialogBody className="max-h-72 space-y-3 leading-relaxed">
              {Array.from({ length: 6 }, (_, i) => (
                <p key={i}>
                  {i + 1}. You keep ownership of everything you create. We only process it to run the service, and you can
                  export or delete it at any time.
                </p>
              ))}
            </DialogBody>
            <DialogFooter>
              <Button onPress={close}>I agree</Button>
            </DialogFooter>
          </>
        )}
      </Dialog>
    </DialogTrigger>
  ),
}

Source: packages/ui/src/dialog/dialog.tsx

Catalog

Category
overlays
Priority
Foundation
Primitive
react-aria
React Aria
Modal + Dialog
Base UI
Dialog