Skip to content

Overlays & navigation

Dialog

Dialogs interrupt the page for a focused task or a decision. Plinth builds them on the native <dialog> element, so the browser handles the focus trap, the inert background and Escape; the component adds sizes, a header that names the dialog for screen readers, a scrolling body, a footer that stacks on phones, and a ready-made confirm pattern for destructive actions.

On this page

Invite to Fernhill Studio

Teammates get access to every project in this workspace.

Separate several addresses with commas.

Usage

components/ui/dialog.tsx
import { useState } from "react";import { Button } from "@/components/ui/button";import { Dialog, DialogBody, DialogClose, DialogFooter, DialogHeader } from "@/components/ui/dialog"; export function Example() {  const [open, setOpen] = useState(false);  return (    <>      <Button onClick={() => setOpen(true)}>Open</Button>      <Dialog open={open} onClose={() => setOpen(false)}>        <DialogHeader title="Archive invoice" description="It moves to Archive and stops sending reminders." />        <DialogFooter>          <DialogClose><Button variant="outline">Cancel</Button></DialogClose>          <Button variant="primary" onClick={() => setOpen(false)}>Archive</Button>        </DialogFooter>      </Dialog>    </>  );}

Examples

Form dialog

Header with an icon, form fields in the body, actions in the footer. `data-autofocus` picks the field that gets focus on open.

Invite to Fernhill Studio

Teammates get access to every project in this workspace.

Separate several addresses with commas.

Destructive confirmation

ConfirmDialog with `tone="danger"` is an alertdialog: a warning icon, a red confirm button, Cancel focused first and no backdrop dismiss. `confirmText` asks for the name before the button enables.

Delete fernhill-web?

This permanently deletes the project, its 14 deployments and all environment variables. This can't be undone.

Simple confirmation

The default tone for decisions that are easy to undo.

Publish to production?

Your 3 changes go live on ledgerly.example for every visitor.

Sizes

Four max widths. On phones every size fills the screen minus a 16px margin.

A null dialog

sm 384px, md 512px, lg 672px, xl 896px. Phones always get the full width minus a margin.

Long content

The body scrolls while the header and footer stay in view. `divider` draws the lines that separate them.

Terms of service

Updated March 4. Please read before continuing.

1. Using Ledgerly

You may use Ledgerly to create, send and track invoices for your business. You are responsible for the accuracy of the information you enter, and for making sure the invoices you send meet the rules where you and your clients are based.

2. Your account

Keep your sign-in details private. You are responsible for activity in your workspace, including activity by teammates you invite. Tell us right away at security@ledgerly.example if you think someone else has access.

3. Payments

Paid plans are billed monthly or yearly in advance. Fees are not refundable, except where the law says otherwise. If a payment fails we retry for 14 days before the workspace becomes read-only.

4. Your data

You own the data you put into Ledgerly. We process it only to provide the service, as described in our privacy notice.

5. Availability

We aim for 99.9% monthly uptime. Planned maintenance is announced at least 48 hours ahead on our status page.

6. Ending the agreement

You can close your workspace at any time from Settings. We keep an export of your data available for 30 days.

7. Changes

If we change these terms, we will tell you by email at least 30 days before the change takes effect.

Modal is the one-component version: title, description, body and footer as props. It renders the same Dialog.

Rename workspace

The name shows in the sidebar and on invoices.

Props

Dialog

  • openrequiredbooleandefault: —Whether the dialog is shown. The component calls showModal() and close() for you.
  • onCloserequired() => voiddefault: —Called for Escape, a backdrop click, the close button and DialogClose. Set open to false here.
  • size"sm" | "md" | "lg" | "xl"default: "md"Max width: 384, 512, 672 or 896px.
  • role"dialog" | "alertdialog"default: "dialog"Use alertdialog for confirmations. It also turns off backdrop dismiss by default.
  • position"center" | "top"default: "center"Top pins the dialog near the top of the screen, like a command palette.
  • closeOnBackdropbooleandefault: trueClose when the backdrop is clicked. Defaults to false for alertdialog.
  • closeOnEscapebooleandefault: trueClose on Escape. Turn off only while a task is in progress.
  • aria-labelstringdefault: —Name for dialogs without a DialogHeader.

DialogHeader

  • titlerequiredReactNodedefault: —Heading. Also the dialog's accessible name.
  • descriptionReactNodedefault: —Supporting text, linked with aria-describedby.
  • iconReactNodedefault: —Lucide icon in a tinted square before the title.
  • tone"neutral" | "accent" | "danger"default: "neutral"Tint of the icon square.
  • hideClosebooleandefault: falseHide the × button (the footer must offer a way out).
  • dividerbooleandefault: falseLine under the header, for scrolling bodies.

DialogBody, DialogFooter, DialogClose

  • DialogBody{ className, children }default: —Content area. Scrolls when the dialog is taller than the screen.
  • DialogFooter{ divider?, className, children }default: —Actions. Stacked full-width on phones (primary on top), right-aligned from 640px.
  • DialogClose{ children: ReactElement }default: —Wraps a button so a click closes the dialog.
  • useDialog()() => { close }default: —Close the surrounding dialog from any child component.

ConfirmDialog

  • open / onCloserequiredboolean / () => voiddefault: —Same as Dialog.
  • onConfirmrequired() => voiddefault: —Called when the confirm button is pressed; the dialog then closes.
  • titlerequiredstringdefault: —The question, e.g. “Delete fernhill-web?”.
  • descriptionReactNodedefault: —What happens and whether it can be undone.
  • tone"default" | "danger"default: "default"Danger adds a warning icon, a red button and focuses Cancel first.
  • confirmTextstringdefault: —Text the user must type before confirming.
  • confirmLabel / cancelLabelstringdefault: "Confirm" / "Cancel"Button labels. Use a verb that names the action.

Accessibility

  • Built on <dialog> with showModal(): the rest of the page is inert, Tab stays inside, and focus returns to the trigger on close.
  • Escape closes the dialog. For alertdialogs a backdrop click does not, so a stray click can't confirm or cancel by accident.
  • DialogHeader's title names the dialog (aria-labelledby) and the description is read with it (aria-describedby). Header-less dialogs need aria-label.
  • On open, focus goes to the element with data-autofocus, otherwise to the first focusable element. Destructive confirmations focus Cancel.
  • The × button has the accessible name “Close”. Page scrolling is locked while a dialog is open.

Suggested