Modal

Modal is a dialog component displayed as an overlay above the main content — available in two types: standard Modal and AlertModal for actions requiring explicit user confirmation.

2
Types
6
Sizes
2
Scroll Mode
Radix
Base

Playground

Preview
SMModal
Modal
Type
Size
Scroll
Content
Options
Button Style
Cancel
Confirm
const [open, setOpen] = useState(false)

<Modal open={open} onOpenChange={setOpen}>
  <Modal.Trigger asChild>
    <Button variant="outline">Open Modal</Button>
  </Modal.Trigger>
  <Modal.Content>
    <Modal.Header>
      <Modal.Title>Modal Title</Modal.Title>
      <Modal.Description>This is a description of the modal.</Modal.Description>
    </Modal.Header>
    <Modal.Body>
      <p>This is a modal dialog. You can place any content here, including forms, confirmations, or informational messages.</p>
    </Modal.Body>
    <Modal.Footer>
      <Modal.Close asChild>
        <Button variant="ghost" size="md" pressEffect={false}>Cancel</Button>
      </Modal.Close>
      <Button size="md" pressEffect={false}>Confirm</Button>
    </Modal.Footer>
  </Modal.Content>
</Modal>

Sizes

xsSimple confirmation or alert
max-width360px
sm (default)Standard (default)
max-width480px
mdStandard form
max-width600px
lgComplex form
max-width760px
xlTable or dashboard
max-width960px
fullFull width
max-widthFull
Padding (all sizes)
Header24px · bottom 0
Body24px
Footer24px · top 0
Closeright 16px, top 16px

Features

Scroll Behavior

"outside": entire modal scrolls within the viewport. "inside": only Body scrolls — Header and Footer stay fixed.

{/* Outside scroll — entire modal scrolls within viewport */}
<Modal.Content scrollBehavior="outside">
  <Modal.Header>
    <Modal.Title>Outside Scroll</Modal.Title>
    <Modal.Description>The entire modal scrolls within the viewport.</Modal.Description>
  </Modal.Header>
  <Modal.Body>
    <p>Long content here...</p>
  </Modal.Body>
</Modal.Content>

{/* Inside scroll — only body scrolls; header stays fixed */}
<Modal.Content scrollBehavior="inside">
  <Modal.Header>
    <Modal.Title>Inside Scroll</Modal.Title>
    <Modal.Description>Only the body scrolls; header and footer stay fixed.</Modal.Description>
  </Modal.Header>
  <Modal.Body>
    <p>Long content here...</p>
  </Modal.Body>
</Modal.Content>

Sidebar Layout

Available for lg / xl / full sizes. Split Modal.Body with p-0 flex to create a left-nav + right-content layout.

const [active, setActive] = useState(0)
const navItems = ['General', 'Security', 'Notifications']

<Modal.Content size="lg" scrollBehavior="inside" showCloseButton={false}>
  <Modal.Body className="p-0 flex min-h-0">
    {/* Left sidebar */}
    <div className="w-48 shrink-0 flex flex-col">
      <div className="p-3">
        <Modal.Close asChild>
          <Button variant="ghost" size="md" pressEffect={false} className="w-9 h-9 p-0">
            <X className="icon-sm" />
            <span className="sr-only">Close</span>
          </Button>
        </Modal.Close>
      </div>
      <nav className="flex-1 px-2 pb-4 space-y-1">
        {navItems.map((item, i) => (
          <button key={item} onClick={() => setActive(i)}
            className={cn("w-full text-left px-3 py-2 text-md rounded-lg transition-colors",
              active === i
                ? "bg-background-muted text-foreground font-semibold"
                : "text-text-muted hover:text-foreground hover:bg-background-muted"
            )}>
            {item}
          </button>
        ))}
      </nav>
    </div>
    <Divider orientation="vertical" color="muted" className="mx-0 h-auto" />
    {/* Right content */}
    <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
      <div className="px-6 pt-6 pb-4 shrink-0">
        <h2 className="text-lg font-semibold">{navItems[active]}</h2>
      </div>
      <div className="flex-1 px-6 pb-6 overflow-y-auto">
        <p className="text-md text-text-muted">Content area...</p>
      </div>
    </div>
  </Modal.Body>
</Modal.Content>

Close Button

Set to false to hide the close button. Use closeIcon prop to replace the default icon.

{/* With close button (default) — X only */}
<Modal.Content showCloseButton>
  <Modal.Header>...</Modal.Header>
  <Modal.Body>...</Modal.Body>
</Modal.Content>

{/* Without close button — footer actions provide exit */}
<Modal.Content showCloseButton={false}>
  <Modal.Header>...</Modal.Header>
  <Modal.Body>...</Modal.Body>
  <Modal.Footer>
    <Modal.Close asChild>
      <Button variant="ghost" size="md" pressEffect={false}>Cancel</Button>
    </Modal.Close>
    <Button size="md" pressEffect={false}>Confirm</Button>
  </Modal.Footer>
</Modal.Content>

API

Component Structure

ModalRadix Dialog
.Trigger.ContentProps.Header.Title.Description.Body.Footer.CloseProps.Overlay.Portal
AlertModalRadix AlertDialog
.Trigger.ContentProps.Header.Title.Description.Body.Footer.Action.Cancel.Overlay.Portal

Props

Modal.Content

size"sm"
"xs" | "sm" | "md" | "lg" | "xl" | "full"

Maximum content width (360/480/600/760/960/full)

scrollBehavior"outside"
"inside" | "outside"

Scroll position when content overflows

showCloseButtontrue
boolean

Show the built-in close button

closeIconBuilt-in SVG icon
ReactNode

Custom close icon

AlertModal.Content

size"sm"
"xs" | "sm"

Maximum content width (360/480)

Modal.Close

asChildfalse
boolean

Use child element as the trigger

Anatomy

1
2
3
4
Modal title
5
Modal description
6
7
8
1
Overlay
Background overlay
2
Content
Dialog panel
3
Header
Title area
4
Title
Heading text
5
Description
Supplemental description
6
Close
Close button
7
Body
Main content
8
Footer
Action buttons

Best Practices

Recommended

  • Use AlertModal for destructive actions
  • Always set a title and description for the modal
  • Use scrollBehavior="inside" for long content
  • Choose a size appropriate for the content

Don't

  • Don't nest a modal inside another modal
  • Don't use a modal for simple notifications — use Toast instead
  • Don't create a modal with no way to close it
  • Don't use AlertModal for routine interactions — reserve it for actions requiring confirmation

Accessibility

Keyboard

Press Escape to close the modal. AlertModal does not close on overlay click. Tab key traps focus inside the modal, preventing it from moving to elements outside.

ARIA / WCAG

  • role="dialog" Set automatically by Radix Dialog
  • role="alertdialog" Set automatically for AlertModal
  • aria-labelledby Auto-linked to Modal.Title
  • aria-describedby Auto-linked to Modal.Description
  • Focus trap — restricts focus within the modal
  • sr-only label on the close button