Installation
Theme
Mode

Overlays & feedback

Modal dialog

A focused overlay for a single decision or a short task — confirmations, destructive actions, and quick forms. Dims the page behind a scrim, traps focus, and closes on Escape or scrim click.

$npx shadcn@latest add @orbit/modal

Overview

Live — click to open a real dialog. Each closes on Escape, the scrim, Cancel, or the × in the corner.

Form, invite, and destructive dialogs

Anatomy

A dialog is a scrim plus a card with three regions: a header (optional tinted icon, title, subtitle, close ×), a body that scrolls independently when tall, and a footer of actions aligned right. The card is centered, rounded 14px, elevated, and animates in with a subtle rise and fade.

ModalRoot — holds open state; dismissable controls scrim clicks.
ModalTriggerOpens the dialog. Pass render={<Button …/>} to restyle.
ModalContentScrim + centered card. size: sm · md · lg · xl.
ModalHeaderOptional tinted icon (tone), children, and the × close button.
ModalTitle / ModalDescriptionAccessible name and description for the dialog.
ModalBodyThe message or form. Scrolls independently if the content is tall.
ModalFooterRight-aligned actions, with an optional left meta slot.
ModalCloseCloses from anywhere inside — footer buttons, links, the ×.

Common dialogs

The dialogs a project app needs most — all live.

Confirm — a plain question with a safe way out
Destructive — red treatment, named consequence, type-to-confirm gate
Form — title + subtitle header, mixed-width fields
Success — tinted icon, no ×, a single Done action

Sizes

Match the width to the content: sm 400px for confirmations, md 496px for short forms, lg 580px and xl 680px for richer flows. Max height is 100vh − 48px.

Long content

The body scrolls on its own while the header and footer stay pinned, so actions are always reachable.

Dismissal

By default the scrim, Escape, the ×, and Cancel all close the dialog. Set dismissable={false} to ignore scrim clicks when an accidental tap would lose meaningful work — Escape and explicit buttons still work.

Props

<Modal>

PropTypeDefaultDescription
openbooleanControlled open state.
defaultOpenbooleanfalseInitial open state when uncontrolled.
onOpenChange(open: boolean) => voidCalled when the dialog opens or closes.
dismissablebooleantruefalse → scrim clicks no longer close the dialog; Escape still does.

<ModalContent>

PropTypeDefaultDescription
size"sm" | "md" | "lg" | "xl""md"Card width: 400 / 496 / 580 / 680px.
keepMountedbooleanfalseKeep the dialog mounted in the DOM while closed.
…restHTMLAttributes<div>Forwarded to the dialog card, including ref.

<ModalHeader>

PropTypeDefaultDescription
iconReactNode22px glyph rendered in a 38px tinted square before the title.
tone"neutral" | "blue" | "red" | "green" | "amber""blue"Tint pair for the icon square.
showClosebooleantrueHide the × for dialogs with a single explicit action.

<ModalFooter>

PropTypeDefaultDescription
metaReactNodeLeft-aligned helper text, e.g. “4 of 10 seats used”.

<ModalTrigger> / <ModalClose>

PropTypeDefaultDescription
renderReactElementRender a custom element (usually a Button) instead of the plain <button>. All raw Base UI parts are also exported as ModalPrimitive.

Usage

Modals interrupt — use them only when focus is warranted.

Do

Reserve modals for one focused task

A confirmation, a destructive action, or a short form — not a place to browse or read.

Don’t

Stack modals on modals

Opening a dialog from a dialog buries context. Close the first or use a different surface.

Do

Guard destructive actions

Use the red treatment, name the consequence, and require type-to-confirm for the irreversible ones.

Don’t

Put long content in a modal

If it scrolls a lot or needs deep focus, use a full page or a side panel instead.

Keyboard & accessibility

Built on Base UI Dialog: role='dialog' with aria-modal, focus trapped inside while open and returned to the trigger on close, page scroll locked, and the title/description wired up as the accessible name.

EscClose the dialog
TabCycle focus within the dialog
Enter / SpaceActivate the focused button
Click scrimDismiss (when dismissable)
aria-modalSet to true
FocusReturns to the trigger on close