Skip to content

Dialog

Modal window that asks for focus, such as a confirmation or a short form. Open it with DialogTrigger.

Overlayv0.1.0experimentalWCAG 2.2 AAView spec
import { Button, Dialog, DialogTrigger } from "@rdloom/react";

export default function DialogConfirmExample() {
  return (
    <DialogTrigger>
      <Button variant="secondary">Discard draft</Button>
      <Dialog title="Discard draft?" description="Your changes will be lost." size="sm">
        {({ close }) => (
          <div className="flex justify-end gap-2">
            <Button variant="secondary" onPress={close}>
              Keep editing
            </Button>
            <Button onPress={close}>Discard</Button>
          </div>
        )}
      </Dialog>
    </DialogTrigger>
  );
}

Installation

npx rdloom add dialog

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/dialog.json

Works, but without upgrade tracking.

Usage

import { Button, Dialog, DialogTrigger } from "@rdloom/react";

<DialogTrigger>
  <Button variant="secondary">Discard draft</Button>
  <Dialog title="Discard draft?" description="Your changes will be lost." size="sm">
    {({ close }) => (
      <div className="flex justify-end gap-2">
        <Button variant="secondary" onPress={close}>
          Keep editing
        </Button>
        <Button onPress={close}>Discard</Button>
      </div>
    )}
  </Dialog>
</DialogTrigger>

Form

import { Button, Dialog, DialogTrigger, TextField } from "@rdloom/react";

export default function DialogFormExample() {
  return (
    <DialogTrigger>
      <Button>Invite member</Button>
      <Dialog title="Invite member" description="They'll get an email with a link.">
        {({ close }) => (
          <form
            className="flex flex-col gap-4"
            onSubmit={(e) => {
              e.preventDefault();
              close();
            }}
          >
            <TextField label="Email" type="email" isRequired autoFocus />
            <div className="flex justify-end gap-2">
              <Button variant="secondary" onPress={close}>
                Cancel
              </Button>
              <Button type="submit">Send invite</Button>
            </div>
          </form>
        )}
      </Dialog>
    </DialogTrigger>
  );
}

Alert

import { Button, Dialog, DialogTrigger } from "@rdloom/react";

export default function DialogAlertExample() {
  return (
    <DialogTrigger>
      <Button variant="danger">Delete project</Button>
      {/* alertdialog: screen readers announce it as urgent. Not dismissable by clicking outside. */}
      <Dialog title="Delete project?" description="This permanently removes the project and its data." role="alertdialog" size="sm">
        {({ close }) => (
          <div className="flex justify-end gap-2">
            <Button variant="secondary" onPress={close}>
              Cancel
            </Button>
            <Button variant="danger" onPress={close}>
              Delete
            </Button>
          </div>
        )}
      </Dialog>
    </DialogTrigger>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
titlerequired

Heading, also used as the dialog's accessible name.

stringnone
description

Short supporting text under the title.

stringnone
size

Maximum width.

"sm" | "md" | "lg""md"
role

Use alertdialog for destructive or urgent confirmations.

"dialog" | "alertdialog""dialog"
portalContainer

Draw the panel and its backdrop inside this element instead of over the whole page. Use it to show a sheet inside a preview, a framed app or a phone mock. The element must be positioned (relative) and clip its overflow.

Element | nullnone
headerEnd

Small content at the end of the header line, next to the title, such as a count.

ReactNodenone
isDismissable

Closes on backdrop click. Esc always closes.

booleantrue
children

Body and actions. A function receives close().

ReactNode | ((opts: { close: () => void }) => ReactNode)none

Accessibility

Role dialog, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Esc closes
  • Tab and Shift+Tab stay inside the dialog

Screen readers announce

  • Opening moves focus into the dialog and announces it as a dialog (alert dialog for Delete project) with its title
  • The description is read after the title
  • Browse/virtual cursor mode can't wander into the page behind the dialog
  • Closing announces the button that opened it (focus is back there)

What your code must do

  • Focus moves into the dialog on open and returns to the trigger on close
  • Content behind the dialog is hidden from assistive technology
  • Page scroll is locked while open

Guidelines

Use it when

  • Confirming a destructive action
  • A short task that must finish before continuing

Avoid it when

  • Non-blocking info: use Toast
  • Long or complex flows: use a page

Don't

  • Dialogs opening other dialogs
  • Vague buttons like 'OK' for destructive actions

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-overlay-backdrop
  • --rd-color-surface-raised
  • --rd-color-border-default
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-radius-overlay