Modal
A dialog component for displaying content in an overlay. Built on Radix UI Dialog for accessibility and focus management.
State Variants (Horizontal)
Vertical Alignment
Footer Additions
Basic Usage
import { Modal } from '@/components/Modal'
import { Button } from '@/components/Button'
<Modal>
<Modal.Trigger asChild>
<Button>Open Modal</Button>
</Modal.Trigger>
<Modal.Content>
<Modal.Header
state="warning"
title="Confirm Action"
description="Are you sure you want to proceed?"
/>
<Modal.Footer>
<Button variant="secondary" appearance="stroke">Cancel</Button>
<Button variant="primary">Continue</Button>
</Modal.Footer>
</Modal.Content>
</Modal>State Variants
The Modal supports different states for various use cases.
Default
Standard modal without any state indicator.
<Modal.Header
state="default"
title="Basic Modal"
description="This is a simple modal dialog."
/>Error
For destructive actions or error messages.
<Modal.Header
state="error"
title="Delete Item"
description="This action cannot be undone."
/>
<Modal.Footer>
<Button variant="secondary" appearance="stroke">Cancel</Button>
<Button variant="destructive">Delete</Button>
</Modal.Footer>Warning
For caution messages and confirmations.
<Modal.Header
state="warning"
title="Unsaved Changes"
description="You have unsaved changes that will be lost."
/>Success
For success messages and confirmations.
<Modal.Header
state="success"
title="Payment Complete"
description="Your transaction was processed successfully."
/>Info
For informational messages.
<Modal.Header
state="info"
title="New Features"
description="Check out the latest updates to your account."
/>Alignment
The Modal header supports two alignment options.
Horizontal (Default)
Icon on the left, close button on the right.
<Modal.Header
alignment="horizontal"
state="warning"
title="Confirm Action"
description="Are you sure you want to proceed?"
showCloseButton
/>Vertical
Centered content, ideal for focused dialogs.
<Modal.Header
alignment="vertical"
state="success"
title="Success!"
description="Your changes have been saved."
/>Custom Icon
Override the default state icon with a custom icon.
<Modal.Header
state="default"
title="Settings"
description="Configure your preferences."
icon={<SettingsIcon />}
/>Footer Layouts
The Modal footer supports different button layouts.
Double (Default)
Two buttons with equal width.
<Modal.Footer layout="double">
<Button variant="secondary" appearance="stroke" className="flex-1">
Cancel
</Button>
<Button variant="primary" className="flex-1">
Continue
</Button>
</Modal.Footer>Single
Single full-width button.
<Modal.Footer layout="single">
<Button variant="primary" className="w-full">
Confirm
</Button>
</Modal.Footer>Right
Buttons aligned to the right with optional left addition.
<Modal.Footer layout="right">
<Button variant="secondary" appearance="stroke">Cancel</Button>
<Button variant="primary">Save</Button>
</Modal.Footer>Footer Additions
Add extra elements to the footer left side (with layout="right").
Checkbox
<Modal.Footer
layout="right"
addition="checkbox"
additionLabel="Don't show again"
onAdditionChange={(checked) => console.log(checked)}
>
<Button variant="secondary" appearance="stroke">Cancel</Button>
<Button variant="primary">Save</Button>
</Modal.Footer>Toggle
<Modal.Footer
layout="right"
addition="toggle"
additionLabel="Enable notifications"
>
<Button variant="secondary" appearance="stroke">Cancel</Button>
<Button variant="primary">Save</Button>
</Modal.Footer>Progress
<Modal.Footer
layout="right"
addition="progress"
current={1}
total={4}
>
<Button variant="secondary" appearance="stroke">Back</Button>
<Button variant="primary">Next</Button>
</Modal.Footer>Extra Button
<Modal.Footer
layout="right"
addition="extraButton"
extraButtonLabel="Reset"
onExtraButtonClick={() => console.log('reset')}
>
<Button variant="secondary" appearance="stroke">Cancel</Button>
<Button variant="primary">Save</Button>
</Modal.Footer>Controlled Mode
Control the modal open state externally.
const [open, setOpen] = useState(false)
<Modal open={open} onOpenChange={setOpen}>
<Modal.Content>
<Modal.Header title="Controlled Modal" description="..." />
<Modal.Footer>
<Button onClick={() => setOpen(false)}>Close</Button>
</Modal.Footer>
</Modal.Content>
</Modal>
<Button onClick={() => setOpen(true)}>Open Modal</Button>Custom Body Content
Use Modal.Body for custom content between header and footer.
<Modal>
<Modal.Content>
<Modal.Header title="Form" description="Fill out the details below." />
<Modal.Body>
<form className="space-y-4">
<input type="text" placeholder="Name" />
<input type="email" placeholder="Email" />
</form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" appearance="stroke">Cancel</Button>
<Button variant="primary">Submit</Button>
</Modal.Footer>
</Modal.Content>
</Modal>Granular Control
Build custom headers using individual components.
<Modal>
<Modal.Content>
<Modal.Header state="info" alignment="vertical">
<Modal.Icon state="info" />
<Modal.Title>Custom Header</Modal.Title>
<Modal.Description>
With completely custom content and structure.
</Modal.Description>
</Modal.Header>
<Modal.Footer>
<Button variant="primary">Got it</Button>
</Modal.Footer>
</Modal.Content>
</Modal>Accessibility
- Built on Radix UI Dialog for robust focus management
- Proper ARIA attributes (
role="dialog",aria-modal) - Focus trap within the modal
- Auto-focus on first focusable element
- Closes on Escape key press
- Closes on overlay click
- Screen reader friendly with title and description
API Reference
Modal / Modal.Root Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | - | Controlled open state |
| defaultOpen | boolean | false | Default open state |
| onOpenChange | (open: boolean) => void | - | Callback when open state changes |
| state | 'default' | 'error' | 'warning' | 'success' | 'info' | 'default' | Visual state variant |
| size | 'sm' | 'md' | 'lg' | 'md' | Modal size |
| alignment | 'horizontal' | 'vertical' | 'horizontal' | Header alignment |
Modal.Content Props
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes |
Modal.Header Props
| Prop | Type | Default | Description |
|---|---|---|---|
| state | 'default' | 'error' | 'warning' | 'success' | 'info' | Context value | Visual state variant |
| size | 'sm' | 'md' | 'lg' | Context value | Icon size |
| alignment | 'horizontal' | 'vertical' | Context value | Layout alignment |
| title | string | - | Title text (shorthand) |
| description | string | - | Description text (shorthand) |
| icon | React.ReactNode | - | Custom icon (overrides state icon) |
| showCloseButton | boolean | false | Show close button |
| className | string | - | Additional CSS classes |
| children | React.ReactNode | - | Custom content (overrides title/description) |
Modal.Body Props
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes |
| children | React.ReactNode | - | Body content |
Modal.Footer Props
| Prop | Type | Default | Description |
|---|---|---|---|
| layout | 'double' | 'single' | 'right' | 'double' | Button layout |
| addition | 'none' | 'checkbox' | 'toggle' | 'progress' | 'extraButton' | 'none' | Left-side addition |
| additionLabel | string | - | Label for checkbox/toggle |
| additionChecked | boolean | - | Controlled checked state |
| additionDefaultChecked | boolean | - | Default checked state |
| onAdditionChange | (checked: boolean) => void | - | Checkbox/toggle change handler |
| current | number | 0 | Current step for progress (0-indexed) |
| total | number | 3 | Total steps for progress |
| extraButtonLabel | string | 'Button' | Extra button label |
| onExtraButtonClick | () => void | - | Extra button click handler |
| className | string | - | Additional CSS classes |
Modal.Title Props
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes |
| children | React.ReactNode | - | Title content |
Modal.Description Props
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes |
| children | React.ReactNode | - | Description content |
Modal.Icon Props
| Prop | Type | Default | Description |
|---|---|---|---|
| state | 'default' | 'error' | 'warning' | 'success' | 'info' | Context value | Icon state |
| size | 'sm' | 'md' | 'lg' | Context value | Icon size |
| className | string | - | Additional CSS classes |
| children | React.ReactNode | - | Custom icon content |
Modal.Close Props
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes |
| asChild | boolean | false | Merge props with child element |
Modal.Trigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Merge props with child element |