import {
Modal,
ModalTrigger,
ModalContent,
ModalTitle,
ModalDescription,
ModalBody,
ModalFooter,
ModalClose,
ModalAction,
} from "@/components/ui/modal"
Modal
A flexible, accessible dialog system that can render as either a traditional modal dialog on desktop devices or a bottom drawer on mobile devices.
Playground
Customize the Modal properties to see different variations.
Customize
Installation
pnpm dlx shadcn@latest add https://shadcn-ui-variants.vercel.app/r/modal.json
Overview
The Modal component it's built on top of Radix UI's Dialog primitive and Vaul's Drawer primitive, providing a consistent API across both presentation modes.
Key Features
- Responsive Design: Automatically switches between a centered modal on desktop and a bottom drawer on mobile
- Multiple Variants: Supports different visual styles (default, success, destructive, warning)
- Customizable Layout: Configurable header, body, and footer sections
- Icon Support: Optional icons for different modal types
- Alignment Options: Content can be left-aligned or centered
- Accessibility: Built with accessibility in mind, including keyboard navigation and screen reader support
Basic Usage
<Modal>
<ModalTrigger asChild>
<Button>Open Modal</Button>
</ModalTrigger>
<ModalContent>
<ModalTitle>Modal Title</ModalTitle>
<ModalDescription>This is a description of what this modal does.</ModalDescription>
<ModalBody>
<p>This is the main content of the modal.</p>
</ModalBody>
<ModalFooter>
<ModalClose>Cancel</ModalClose>
<ModalAction onClick={() => console.log("Action clicked")}>Confirm</ModalAction>
</ModalFooter>
</ModalContent>
</Modal>
Examples
Centered Modal with custom icon
Dynamic Modal with separated header
Form Modal in 'alertdialog' mode
Component Anatomy
ModalTrigger
The element that triggers the modal to open:
<ModalTrigger>
Open Modal
</ModalTrigger>
By using only ModalTrigger
, you can pass all the same props that you would pass to a Button
component:
<ModalTrigger
iconRight={<ArrowRight />}
iconAnimation="translateXRight"
>
Open Modal
</ModalTrigger>
Or you can wrap a Button
passsing asChild
:
<ModalTrigger asChild>
<Button>Open Modal</Button>
</ModalTrigger>
ModalContent
The container for the modal content.
<ModalContent>
{/* Modal content goes here */}
</ModalContent>
ModalTitle
The title of the modal.
<ModalTitle>Modal Title</ModalTitle>
ModalDescription
A description or subtitle for the modal.
<ModalDescription>
This is a description of what this modal does.
</ModalDescription>
ModalBody
The main content area of the modal.
<ModalBody>
<p>This is the main content of the modal.</p>
</ModalBody>
ModalFooter
The footer area of the modal, typically containing action buttons.
<ModalFooter>
<ModalClose>Cancel</ModalClose>
<ModalAction>Confirm</ModalAction>
</ModalFooter>
ModalAction
A button for the primary action in the modal.
<ModalAction onClick={handleAction}>Confirm</ModalAction>
ModalClose
A button that closes the modal. It is also a Button
under the hood.
<ModalClose>Cancel</ModalClose>
Accessibility
The Modal component is built with accessibility in mind:
- Keyboard Navigation: Users can navigate through the modal using the Tab key and close it using the Escape key.
- Focus Management: When the modal opens, focus is automatically moved to the first focusable element within the modal. When closed, focus returns to the element that triggered the modal.
- ARIA Attributes: The component uses appropriate ARIA roles and attributes to ensure screen readers can properly announce the modal and its content.
- Alert Dialog Mode: For critical actions, use the `mode="alertdialog"` prop to prevent users from accidentally dismissing the modal by clicking outside or pressing Escape.
Modal Props
Prop | Type | Default |
---|---|---|
children* | React.ReactNode | — |
className | string | — |
asChild | boolean | — |
open | boolean | — |
onOpenChange | function | — |
separatedHeader | boolean | false |
separatedFooter | boolean | false |
variant | enum | "default" |
withIcon | boolean | false |
align | enum | "left" |
customIcon | React.ReactElement | — |
mode | enum | "dialog" |
showCloseButton | boolean | false |
responsive | boolean | true |