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.

Dialog API Reference

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

import { Modal, ModalTrigger, ModalContent, ModalTitle, ModalDescription, ModalBody, ModalFooter, ModalClose, ModalAction, } from "@/components/ui/modal"
<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:

  1. Keyboard Navigation: Users can navigate through the modal using the Tab key and close it using the Escape key.
  2. 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.
  3. ARIA Attributes: The component uses appropriate ARIA roles and attributes to ensure screen readers can properly announce the modal and its content.
  4. 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

PropTypeDefault
children*
React.ReactNode
className
string
asChild
boolean
open
boolean
onOpenChange
function
separatedHeader
booleanfalse
separatedFooter
booleanfalse
variant
enum"default"
withIcon
booleanfalse
align
enum"left"
customIcon
React.ReactElement
mode
enum"dialog"
showCloseButton
booleanfalse
responsive
booleantrue