Modal
Dialog overlay for focused user interactions and important content
Import
import {Modal} from "@heroui/react";Usage
"use client";
import {Button, Modal} from "@heroui/react";
import {Icon} from "@iconify/react";
Anatomy
Import the Modal component and access all parts using dot notation.
import {Modal, Button} from "@heroui/react";
export default () => (
<Modal>
<Button>Open Modal</Button>
<Modal.Container>
<Modal.Dialog>
<Modal.CloseTrigger /> {/* Optional: Close button */}
<Modal.Header>
<Modal.Icon /> {/* Optional: Icon */}
<Modal.Heading />
</Modal.Header>
<Modal.Body />
<Modal.Footer />
</Modal.Dialog>
</Modal.Container>
</Modal>
);Placement
"use client";
import {Button, Modal} from "@heroui/react";
import {Icon} from "@iconify/react";
Backdrop Variants
"use client";
import {Button, Modal} from "@heroui/react";
import {Icon} from "@iconify/react";
Dismiss Behavior
isDismissable
Controls whether the modal can be dismissed by clicking the overlay backdrop. Defaults to true. Set to false to require explicit close action.
isKeyboardDismissDisabled
Controls whether the ESC key can dismiss the modal. When set to true, the ESC key will be disabled and users must use explicit close actions.
"use client";
import {Button, Modal} from "@heroui/react";
import {Icon} from "@iconify/react";
Scroll Behavior
"use client";
import {Button, Label, Modal, Radio, RadioGroup} from "@heroui/react";
import {useState} from "react";
With Form
"use client";
import {Button, Input, Label, Modal, Surface, TextField} from "@heroui/react";
import {Icon} from "@iconify/react";
Controlled State
With React.useState()
Control the modal using React's useState hook for simple state management. Perfect for basic use cases.
Status: closed
With useOverlayState()
Use the useOverlayState hook for a cleaner API with convenient methods like open(), close(), and toggle().
Status: closed
"use client";
import {Button, Modal, useOverlayState} from "@heroui/react";
import {Icon} from "@iconify/react";
import {useState} from "react";Custom Trigger
Settings
Manage your preferences
"use client";
import {Button, Modal} from "@heroui/react";
import {Icon} from "@iconify/react";
Custom Backdrop
"use client";
import {Button, Modal} from "@heroui/react";
import {Icon} from "@iconify/react";
Custom Animations
"use client";
import {Button, Modal} from "@heroui/react";
import {Icon} from "@iconify/react";
Styling
Passing Tailwind CSS classes
import {Modal, Button} from "@heroui/react";
function CustomModal() {
return (
<Modal>
<Button>Open Modal</Button>
<Modal.Container backdropClassName="bg-black/80" className="items-start pt-20">
<Modal.Dialog className="bg-gradient-to-br from-purple-500 to-pink-500 text-white">
<Modal.Header>
<h2>Custom Styled Modal</h2>
</Modal.Header>
<Modal.Body>
<p>This modal has custom styling applied via Tailwind classes</p>
</Modal.Body>
<Modal.Footer>
<Button>Close</Button>
</Modal.Footer>
</Modal.Dialog>
</Modal.Container>
</Modal>
);
}Customizing the component classes
To customize the Modal component classes, you can use the @layer components directive.
@layer components {
.modal__backdrop {
@apply bg-gradient-to-br from-black/50 to-black/70;
}
.modal__dialog {
@apply rounded-2xl border border-white/10 shadow-2xl;
}
.modal__header {
@apply text-center;
}
.modal__close-trigger {
@apply rounded-full bg-white/10 hover:bg-white/20;
}
}HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The Modal component uses these CSS classes (View source styles):
Base Classes
.modal__trigger- Trigger element that opens the modal.modal__backdrop- Overlay backdrop behind the modal.modal__container- Positioning wrapper with placement support.modal__dialog- Modal content container.modal__header- Header section for titles and icons.modal__body- Main content area.modal__footer- Footer section for actions.modal__close-trigger- Close button element
Backdrop Variants
.modal__backdrop--opaque- Opaque colored backdrop (default).modal__backdrop--blur- Blurred backdrop with glass effect.modal__backdrop--transparent- Transparent backdrop (no overlay)
Scroll Variants
.modal__container--scroll-outside- Enables scrolling the entire modal.modal__dialog--scroll-inside- Constrains modal height for body scrolling.modal__body--scroll-inside- Makes only the body scrollable.modal__body--scroll-outside- Allows full-page scrolling
Interactive States
The component supports these interactive states:
- Focus:
:focus-visibleor[data-focus-visible="true"]- Applied to trigger, dialog, and close button - Hover:
:hoveror[data-hovered="true"]- Applied to close button on hover - Active:
:activeor[data-pressed="true"]- Applied to close button when pressed - Entering:
[data-entering]- Applied during modal opening animation - Exiting:
[data-exiting]- Applied during modal closing animation - Placement:
[data-placement="*"]- Applied based on modal position (auto, top, center, bottom)
API Reference
Modal
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Trigger and container elements |
Modal.Trigger
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Custom trigger content |
className | string | - | CSS classes |
Modal.Container
| Prop | Type | Default | Description |
|---|---|---|---|
placement | "auto" | "center" | "top" | "bottom" | "auto" | Modal position on screen |
scroll | "inside" | "outside" | "inside" | Scroll behavior |
variant | "opaque" | "blur" | "transparent" | "opaque" | Backdrop overlay style |
isDismissable | boolean | true | Close on backdrop click |
isKeyboardDismissDisabled | boolean | false | Disable ESC key to close |
isOpen | boolean | - | Controlled open state |
onOpenChange | (isOpen: boolean) => void | - | Open state change handler |
backdropClassName | string | (values) => string | - | Backdrop CSS classes |
className | string | (values) => string | - | Container CSS classes |
Modal.Dialog
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | ({close}) => ReactNode | - | Content or render function |
className | string | (values) => string | - | CSS classes |
role | string | "dialog" | ARIA role |
aria-label | string | - | Accessibility label |
aria-labelledby | string | - | ID of label element |
aria-describedby | string | - | ID of description element |
Modal.Header
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Header content |
className | string | - | CSS classes |
Modal.Body
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Body content |
className | string | - | CSS classes |
Modal.Footer
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Footer content |
className | string | - | CSS classes |
Modal.CloseTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child |
children | ReactNode | - | Custom close button |
className | string | (values) => string | - | CSS classes |
useOverlayState Hook
import {useOverlayState} from "@heroui/react";
const state = useOverlayState({
defaultOpen: false,
onOpenChange: (isOpen) => console.log(isOpen),
});
state.isOpen; // Current state
state.open(); // Open modal
state.close(); // Close modal
state.toggle(); // Toggle state
state.setOpen(); // Set state directlyAccessibility
Implements WAI-ARIA Dialog pattern:
- Focus trap: Focus locked within modal
- Keyboard:
ESCcloses (when enabled),Tabcycles elements - Screen readers: Proper ARIA attributes
- Scroll lock: Body scroll disabled when open