v1.0

Modal

Accessible modal dialog built on Radix Dialog, with an overlay, focus trap, and composable header, icon, body, and footer slots.

Pro

Description

Modal is a compound dialog built on Radix Dialog. It renders a focus-trapping overlay that interrupts the page until it is resolved, composed from a root plus sub-parts — ModalTrigger, ModalContent, ModalHeader, ModalIcon, ModalHeaderContent, ModalTitle, ModalDescription, ModalMain, ModalBody, ModalFooter, and ModalClose — so you assemble exactly the layout you need.

The root owns the shared size, variant, placement, and backdrop and hands them to the parts through context, so you set them once on <Modal> and the header, icon, body, and footer follow. ModalContent already renders the portal, the backdrop, and (by default) a corner close button — you never add ModalPortal or ModalBackdrop yourself.

Reach for it for a blocking, focused interaction: a confirmation, a create/edit form, a destructive prompt, or a full-screen task. For content anchored to a trigger that shouldn't block the page, use a Popover; for a passive hint, use a Tooltip.

Modal is a Create UI Pro component. With a Pro seat, npx @create-ui/cli add modal installs it. The previews below are marked with a Pro badge.

Installation

pnpm dlx @create-ui/cli add modal

Anatomy

ModalContent wraps the portal, backdrop, and close button. Put the header and scrollable body inside ModalMain, then place ModalFooter as its sibling inside ModalContent.

<Modal size="md" variant="primary">
  <ModalTrigger asChild>
    <Button>Open</Button>
  </ModalTrigger>
  <ModalContent>
    <ModalMain>
      <ModalHeader>
        <ModalIcon>
          <RiFlashlightFill />
        </ModalIcon>
        <ModalHeaderContent>
          <ModalTitle>System notification</ModalTitle>
          <ModalDescription>
            Review the details and take action.
          </ModalDescription>
        </ModalHeaderContent>
      </ModalHeader>
      <ModalBody>{/* your content */}</ModalBody>
    </ModalMain>
    <ModalFooter>
      <ModalClose asChild>
        <Button variant="neutral-light" appearance="soft">
          Cancel
        </Button>
      </ModalClose>
      <Button>Confirm</Button>
    </ModalFooter>
  </ModalContent>
</Modal>

ModalMain groups the header and body into the white card; ModalFooter sits on the tray below it. Render a ModalTitle (and ideally a ModalDescription) so the dialog is labelled for assistive tech.

Usage

import {
  Modal,
  ModalBody,
  ModalClose,
  ModalContent,
  ModalDescription,
  ModalFooter,
  ModalHeader,
  ModalHeaderContent,
  ModalIcon,
  ModalMain,
  ModalTitle,
  ModalTrigger,
} from "@/components/ui/modal"

The root forwards Radix Dialog props, so control it with open / onOpenChange or leave it uncontrolled with defaultOpen:

<Modal open={open} onOpenChange={setOpen} size="md" variant="danger">
  <ModalContent>{/* ... */}</ModalContent>
</Modal>

Examples

Every example is a Pro preview (marked with a Pro badge). Each renders a trigger button; open it to see the modal.

Sizes

size sets the card width and cascades to the header, icon, body, and footer. sm and md are centered cards; cover fills the viewport (minus a gutter) for full-screen tasks. Long bodies scroll inside ModalBody while the header and footer stay pinned.

Pro

Placement

placement sets where the card sits in the viewport — top, center, bottom, or auto (centered). It positions the card; it is not a margin.

Pro

Backdrop

backdrop sets the overlay treatment: opaque dims the page, blur dims and blurs it, and transparent leaves it visible. Match the weight to the interaction.

Pro

Header alignment

ModalHeader takes an align: horizontal places the icon beside the title and description, vertical-left stacks them left-aligned, and vertical-center stacks and centers them (common for success and confirmation dialogs).

Pro

Icon

ModalIcon renders a decorative glyph whose color follows the modal variant. Tune its look with appearance (solid, soft, neutral, outline), type (stylish adds a gradient fill and ring, plain is flat), and shape (rounded or circle). Put the icon as its single child — the component sizes it.

Pro
primary
neutral
danger
success
warning
info
away

ModalFooter takes align — end clusters the actions to the right, between pushes a secondary control (a checkbox, a link) to the left — and orientation — horizontal or vertical for stacked full-width buttons.

Pro

Close button

ModalContent renders a corner close button by default. Set showCloseButton={false} to hide it; the modal still closes with Escape, an outside click, or any control you wrap in ModalClose.

Pro

Body content

ModalBody is a free slot — compose any of your own components inside it and it scrolls independently while the header and footer stay pinned. The same modal shell can carry a form (Field + Input), a settings panel (Switch toggles), a list (Avatar + Checkbox rows), a media banner (AspectRatio + Badge), or anything else you build — open each below. Radix moves focus to the content container rather than the first field, so add autoFocus to an input yourself if you want it focused on open.

Pro

Accessibility

Modal is built on Radix Dialog, which provides the focus trap, scroll lock, Escape-to-close, and aria wiring.

KeyDescription
TabCycles focus through the focusable elements, trapped in the modal.
Shift TabCycles focus backward.
EscapeCloses the modal.
Space EnterActivates the focused control.

ARIA notes:

  • Render a ModalTitle (and ideally a ModalDescription) inside ModalHeaderContent so the dialog is labelled — Radix logs a warning if a title is missing. If you omit the visible title, supply your own aria-label on ModalContent.
  • Focus is intentionally moved to the content container on open (onOpenAutoFocus is overridden), so the first field is not focused automatically. Add autoFocus to an input when a form should focus it.
  • The corner close button (and any ModalClose) is a real <button>; CloseButton provides its own aria-label.
  • Opening locks page scroll and traps focus; closing restores focus to the trigger.

Styling

Tailwind override: pass className to merge Tailwind classes with the component's CVA classes (via cn()):

<ModalContent className="max-w-2xl">{/* ... */}</ModalContent>

Data slots and attributes: the components set these for CSS targeting:

  • data-slot="modal-trigger" on the trigger.
  • data-slot="modal-backdrop" on the overlay, with data-backdrop.
  • data-slot="modal-container" on the positioning layer, with data-size and data-placement.
  • data-slot="modal-content" on the card, with data-size, data-placement, and data-variant.
  • data-slot="modal-header" on the header, with data-align and data-size; data-slot="modal-header-content" on the title/description column.
  • data-slot="modal-icon" on the icon, with data-variant, data-size, data-appearance, data-type, and data-shape.
  • data-slot="modal-title" and data-slot="modal-description" on the text.
  • data-slot="modal-main" on the card body wrapper and data-slot="modal-body" on the scroll region.
  • data-slot="modal-footer" on the footer, with data-orientation, data-align, and data-size.
  • data-slot="modal-close" on ModalClose.

Target a specific state in CSS:

[data-slot="modal-content"][data-variant="danger"] {
  /* ... */
}
  • Popover: use this for non-blocking content anchored to a trigger, not a page-blocking overlay.
  • Tooltip: use this for a passive hint on hover or focus.
  • Close Button: the dismiss control ModalContent renders and ModalClose wraps.
  • Scroll Area: powers the scrollable ModalBody.
  • Button: the trigger and footer actions.

API Reference

Root that owns the shared context and forwards to Radix Dialog.Root. Extends React.ComponentProps<typeof Dialog.Root>, so open, defaultOpen, onOpenChange, and modal are accepted.

Props

PropTypeDefaultDescription
openboolean-Controlled open state.
defaultOpenbooleanfalseUncontrolled initial open state.
onOpenChange(open: boolean) => void-Fires when the open state changes.
childrenReact.ReactNode-The trigger and content.

Variants

VariantOptionsDefaultDescription
size"sm" "md" "cover""md"Card width; cover fills the viewport.
variant"primary" "neutral" "danger" "success" "warning" "info" "away""primary"Color intent; drives the ModalIcon color.
placement"auto" "top" "center" "bottom""auto"Vertical position of the card.
backdrop"opaque" "blur" "transparent""opaque"Overlay treatment.

ModalTrigger

The element that opens the modal. Wraps Radix Dialog.Trigger; use asChild to render your own Button. Renders <button data-slot="modal-trigger">.

ModalContent

The card. Renders the portal, backdrop, and (by default) the corner close button, then your children. Extends React.ComponentProps<typeof Dialog.Content>.

Props

PropTypeDefaultDescription
showCloseButtonbooleantrueRender the corner close button.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-ModalMain and ModalFooter.

ModalClose

Wraps Radix Dialog.Close; use asChild to turn any element (a footer Button) into a dismiss control.

ModalHeader

The header row inside ModalMain. Provides align/size context to the title and description. Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
align"horizontal" "vertical-left" "vertical-center""horizontal"Layout of the icon and text.
classNamestring-Merged via cn().
childrenReact.ReactNode-ModalIcon and ModalHeaderContent.

ModalHeaderContent

Column that stacks the title and description. Extends React.ComponentProps<"div">.

ModalIcon

Decorative glyph whose color follows the modal variant. Takes the icon as its single child and sizes it (size-7 at md, size-6 at sm). Extends React.ComponentProps<"span">.

Props

PropTypeDefaultDescription
appearance"solid" "soft" "neutral" "outline""solid"Fill treatment.
type"stylish" "plain""stylish"stylish adds a gradient fill and ring; plain is flat.
shape"rounded" "circle""rounded"Corner shape.
variantModalVariantfrom ModalOverrides the inherited color intent.
size"sm" "md"from ModalOverrides the inherited size.
childrenReact.ReactNode-The icon element.

ModalTitle / ModalDescription

Wrap Radix Dialog.Title and Dialog.Description. Render them inside ModalHeaderContent; the title labels the dialog for assistive tech.

ModalMain

Groups the header and body into the white card. Extends React.ComponentProps<"div">.

ModalBody

The scrollable content region. Wraps children in a ScrollArea capped at max-h-100 (uncapped when size="cover") — drop plain content inside, no extra scroll container needed. Extends React.ComponentProps<"div">.

ModalFooter

The action tray below the card. Extends React.ComponentProps<"div"> (the size comes from context).

Props

PropTypeDefaultDescription
orientation"horizontal" "vertical""horizontal"Row of actions or a stacked, full-width column.
align"end" "between""end"Cluster actions right, or split left/right.
classNamestring-Merged via cn().
childrenReact.ReactNode-Footer controls.

ModalBackdrop / ModalPortal

ModalContent renders both automatically — you rarely use them directly. ModalBackdrop (the overlay) takes a backdrop prop that overrides the root's; ModalPortal wraps Radix Dialog.Portal.

Types

type ModalSize = "sm" | "md" | "cover"
type ModalVariant =
  | "primary"
  | "neutral"
  | "danger"
  | "success"
  | "warning"
  | "info"
  | "away"
type ModalPlacement = "auto" | "top" | "center" | "bottom"
type ModalBackdropVariant = "opaque" | "blur" | "transparent"
type ModalHeaderAlign = "horizontal" | "vertical-left" | "vertical-center"
type ModalIconAppearance = "solid" | "soft" | "neutral" | "outline"
type ModalIconType = "stylish" | "plain"
type ModalIconShape = "rounded" | "circle"
type ModalIconSize = "sm" | "md"