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}>
<ModalTrigger asChild>
<Button variant="outline">Open Modal</Button>
</ModalTrigger>
<ModalContent>
<ModalHeader>
<ModalTitle>Modal Title</ModalTitle>
<ModalDescription>This is a description of the modal.</ModalDescription>
</ModalHeader>
<ModalBody>
<p>This is a modal dialog. You can place any content here, including forms, confirmations, or informational messages.</p>
</ModalBody>
<ModalFooter>
<ModalClose asChild>
<Button variant="ghost" size="md" pressEffect={false}>Cancel</Button>
</ModalClose>
<Button size="md" pressEffect={false}>Confirm</Button>
</ModalFooter>
</ModalContent>
</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 */}
<ModalContent scrollBehavior="outside">
<ModalHeader>
<ModalTitle>Outside Scroll</ModalTitle>
<ModalDescription>The entire modal scrolls within the viewport.</ModalDescription>
</ModalHeader>
<ModalBody>
<p>Long content here...</p>
</ModalBody>
</ModalContent>
{/* Inside scroll — only body scrolls; header stays fixed */}
<ModalContent scrollBehavior="inside">
<ModalHeader>
<ModalTitle>Inside Scroll</ModalTitle>
<ModalDescription>Only the body scrolls; header and footer stay fixed.</ModalDescription>
</ModalHeader>
<ModalBody>
<p>Long content here...</p>
</ModalBody>
</ModalContent>Sidebar Layout
Available for lg / xl / full sizes. Split ModalBody with p-0 flex to create a left-nav + right-content layout.
const [active, setActive] = useState(0)
const navItems = ['General', 'Security', 'Notifications']
<ModalContent size="lg" scrollBehavior="inside" showCloseButton={false}>
<ModalBody className="p-0 flex min-h-0">
{/* Left sidebar */}
<div className="w-48 shrink-0 flex flex-col">
<div className="p-3">
<ModalClose 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>
</ModalClose>
</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">
<ModalTitle>{navItems[active]}</ModalTitle>
</div>
<div className="flex-1 px-6 pb-6 overflow-y-auto">
<p className="text-md text-text-muted">Content area...</p>
</div>
</div>
</ModalBody>
</ModalContent>Close Button
Set to false to hide the close button. Use closeIcon prop to replace the default icon.
{/* With close button (default) — X only */}
<ModalContent showCloseButton>
<ModalHeader>
<ModalTitle>Title</ModalTitle>
</ModalHeader>
<ModalBody>...</ModalBody>
</ModalContent>
{/* Without close button — footer actions provide exit */}
<ModalContent showCloseButton={false}>
<ModalHeader>
<ModalTitle>Title</ModalTitle>
</ModalHeader>
<ModalBody>...</ModalBody>
<ModalFooter>
<ModalClose asChild>
<Button variant="ghost" size="md" pressEffect={false}>Cancel</Button>
</ModalClose>
<Button size="md" pressEffect={false}>Confirm</Button>
</ModalFooter>
</ModalContent>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
ModalContent
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 |
AlertModalContent
size"sm""xs" | "sm"Maximum content width (360/480)
| Name | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "sm" | Maximum content width (360/480) |
ModalClose
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
✓
Do
- ✓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
- ✗Reserve AlertModal for actions requiring confirmation — don't use it for routine interactions
Accessibility
Keyboard
EscClose modal
TabMove focus within modal
ARIA / WCAG
role="dialog"Set automatically by Radix Dialogrole="alertdialog"Set automatically for AlertModalaria-labelledbyAuto-linked to ModalTitlearia-describedbyAuto-linked to ModalDescription- Focus trap — restricts focus within the modal
- sr-only label on the close button