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 alertmax-width360px
sm (default)Standard (default)max-width480px
mdStandard formmax-width600px
lgComplex formmax-width760px
xlTable or dashboardmax-width960px
fullFull widthmax-widthFull
Padding (all sizes)
Header24px · bottom 0
Body24px
Footer24px · top 0
Closeright 16px, top 16px
| Size | max-width | Title | Description | Usage |
|---|---|---|---|---|
xs | 360px | 18px semibold | 14px normal | Simple confirmation |
sm | 480px | 18px semibold | 14px normal | Confirmation or alert (default) |
md | 600px | 18px semibold | 14px normal | Standard form |
lg | 760px | 18px semibold | 14px normal | Complex form |
xl | 960px | 18px semibold | 14px normal | Table or dashboard |
full | Full | 18px semibold | 14px normal | Full width |
Padding — all sizes
Header
p-6 pb-0
24px · bottom 0
Body
p-6
24px
Footer
p-6 pt-0
24px · top 0
Close
right-4 top-4
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
.TriggerTrigger element.ContentPropsPanel (size · scroll · close).HeaderHeader area.TitleTitle (aria-labelledby).DescriptionSupplemental text (aria-describedby).BodyContent area.FooterAction area.ClosePropsClose button.OverlayBackground overlay.PortalPortalAlertModalRadix AlertDialog.Trigger.ContentProps.Header.Title.Description.Body.Footer.Action.Cancel.Overlay.Portal
.TriggerTrigger element.ContentPropsPanel (size: xs / sm).HeaderHeader area.TitleTitle (aria-labelledby).DescriptionSupplemental text (aria-describedby).BodyContent area.FooterAction area.ActionConfirm action.CancelCancel.OverlayBackground overlay.PortalPortalProps
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
showCloseButtontruebooleanShow the built-in close button
closeIconBuilt-in SVG iconReactNodeCustom close icon
| Name | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "xl" | "full" | "sm" | Maximum content width (360/480/600/760/960/full) |
scrollBehavior | "inside" | "outside" | "outside" | Scroll position when content overflows |
showCloseButton | boolean | true | Show the built-in close button |
closeIcon | ReactNode | Built-in SVG icon | Custom close icon |
AlertModal.Content
size"sm""xs" | "sm"Maximum content width (360/480)
| Name | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "sm" | Maximum content width (360/480) |
Modal.Close
asChildfalsebooleanUse child element as the trigger
| Name | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | 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 Dialogrole="alertdialog"Set automatically for AlertModalaria-labelledbyAuto-linked to Modal.Titlearia-describedbyAuto-linked to Modal.Description- Focus trap — restricts focus within the modal
- sr-only label on the close button