# Modal
**Category**: react
**URL**: https://www.heroui.com/docs/react/migration/modal
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/react/migration/(components)/modal.mdx
> Migration guide for Modal from HeroUI v2 to v3
***
Refer to the [v3 Modal documentation](/docs/react/components/modal) for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
## Structure Changes
In v2, Modal used separate components:
```tsx
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, useDisclosure } from "@heroui/react";
export default function App() {
const {isOpen, onOpen, onOpenChange} = useDisclosure();
return (
<>
Title
Content
Footer
>
);
}
```
In v3, Modal uses compound components:
```tsx
import { Modal, Button } from "@heroui/react";
export default function App() {
return (
Title
Content
Footer
);
}
```
## Key Changes
### 1. Component Structure
**v2:** Separate components (`Modal`, `ModalContent`, `ModalHeader`, `ModalBody`, `ModalFooter`)
**v3:** Compound components (`Modal.Backdrop`, `Modal.Container`, `Modal.Dialog`, `Modal.Header`, `Modal.Body`, `Modal.Footer`)
### 2. Built-in Trigger with `Modal.Trigger`
**v2:** Required `useDisclosure` hook and manual `onPress` wiring to open the modal
**v3:** Provides `Modal.Trigger` as a built-in trigger component that automatically opens the modal when pressed — no state management needed
```tsx
import { Modal, ModalContent, Button, useDisclosure } from "@heroui/react";
const {isOpen, onOpen, onOpenChange} = useDisclosure();
{/* content */}
```
```tsx
import { Modal, Button } from "@heroui/react";
{/* A Button placed as a direct child of Modal automatically becomes the trigger */}
{/* content */}
```
```tsx
import { Modal } from "@heroui/react";
{/* Use Modal.Trigger for custom trigger elements (cards, links, etc.) */}
Settings
Manage your preferences
{/* content */}
```
`Modal.Trigger` wraps any content in a pressable element that opens the modal. Use it when you need a custom trigger beyond a standard `Button`.
### 3. State Management
**v2:** Uses `useDisclosure` hook
**v3:** Built-in trigger (no state needed), controlled with `isOpen`/`onOpenChange` on `Modal.Backdrop`, or use `useOverlayState` and pass `state` to the root
#### `useOverlayState` replaces `useDisclosure`
The `useOverlayState` hook is a direct replacement for v2's `useDisclosure`. It supports both controlled and uncontrolled modes:
```tsx
import { useOverlayState } from "@heroui/react";
// Uncontrolled (manages its own state)
const state = useOverlayState();
// Uncontrolled with default open
const state = useOverlayState({ defaultOpen: true });
// With callback
const state = useOverlayState({
onOpenChange: (isOpen) => console.log("Modal is now:", isOpen),
});
// Controlled (you manage the state)
const [isOpen, setIsOpen] = useState(false);
const state = useOverlayState({ isOpen, onOpenChange: setIsOpen });
```
**Hook API:**
| Property | Type | Description |
|----------|------|-------------|
| `state.isOpen` | `boolean` | Whether the overlay is currently open |
| `state.open()` | `() => void` | Opens the overlay |
| `state.close()` | `() => void` | Closes the overlay |
| `state.toggle()` | `() => void` | Toggles the overlay open/closed |
| `state.setOpen(isOpen)` | `(isOpen: boolean) => void` | Sets the open state directly |
Pass `state` to the `Modal` root to connect it:
```tsx
const state = useOverlayState();
{/* content */}
```
For detailed migration from `useDisclosure` to `useOverlayState`, see the [Hooks Migration Guide](/docs/react/migration/hooks).
### 4. Prop Changes
| v2 Prop | v3 Location | Notes |
|---------|-------------|-------|
| `size` | `size` (on Container) | Simplified (xs, sm, md, lg, cover, full) |
| `radius` | — | Removed (use Tailwind CSS) |
| `shadow` | — | Removed (use Tailwind CSS) |
| `backdrop` | `variant` (on **Backdrop**) | Renamed; values unchanged (`opaque`, `blur`, `transparent`) |
| `scrollBehavior` | `scroll` (on Container) | Renamed (`normal` → `inside`) |
| `placement` | `placement` (on Container) | Moved to Container |
| `isDismissable` | `isDismissable` (on **Backdrop**) | Moved to `Modal.Backdrop` |
| `isKeyboardDismissDisabled` | `isKeyboardDismissDisabled` (on **Backdrop**) | Moved to `Modal.Backdrop` |
| `isOpen` | `isOpen` (on **Backdrop**) | Controlled state on `Modal.Backdrop` |
| `onOpenChange` | `onOpenChange` (on **Backdrop**) | Same as above |
| `onClose` | — | Use `close` from render prop |
| `hideCloseButton` | — | Omit `Modal.CloseTrigger` instead |
| `closeButton` | — | Use `Modal.CloseTrigger` with custom content |
| `motionProps` | — | Removed (animations handled differently) |
| `classNames` | — | Use `className` props on individual components |
| `shouldBlockScroll` | — | Removed (handled automatically) |
| `portalContainer` | — | Removed |
## Migration Examples
### Controlled Modal
```tsx
import { useDisclosure } from "@heroui/react";
const {isOpen, onOpen, onOpenChange} = useDisclosure();
{(onClose) => (
<>
Title
Content
>
)}
```
```tsx
import { useState } from "react";
const [isOpen, setIsOpen] = useState(false);
{({close}) => (
<>
Title
Content
>
)}
```
```tsx
import { useOverlayState } from "@heroui/react";
const state = useOverlayState();
{({close}) => (
<>
Title
Content
>
)}
```
```tsx
import { useOverlayState } from "@heroui/react";
// Pass state directly to Modal root — no need to wire isOpen/onOpenChange manually
const state = useOverlayState();
{({close}) => (
<>
Title
Content
>
)}
```
For detailed `useDisclosure` → `useOverlayState` migration guide, see the [Hooks Migration Guide](/docs/react/migration/hooks).
### Backdrop and Container Props
```tsx
{/* Backdrop */}
{/* content */}
{/* Placement */}
{/* content */}
{/* Scroll behavior */}
{/* content */}
```
```tsx
{/* Backdrop - use Modal.Backdrop with variant */}
{/* content */}
{/* Placement - on Container */}
{/* content */}
{/* Scroll - on Container */}
{/* content */}
```
### Close Button
```tsx
{/* Without close button */}
{/* content */}
{/* Custom close button */}
}>
{/* content */}
```
```tsx
{/* Without close button - omit Modal.CloseTrigger */}
Title
{/* Custom close button */}
{/* content */}
```
### Custom Trigger
```tsx
import { Modal, ModalContent, useDisclosure } from "@heroui/react";
const {isOpen, onOpen, onOpenChange} = useDisclosure();
{/* Any element needed manual onPress + useDisclosure */}
Settings
Manage your preferences
{/* content */}
```
```tsx
import { Modal } from "@heroui/react";
{/* Modal.Trigger handles press events and accessibility automatically */}
Settings
Manage your preferences
Settings
{/* content */}
```
### With Icon and Heading
```tsx
Modal Title
```
```tsx
Modal Title
```
## Component Anatomy
The v3 Modal follows this structure:
```
Modal (Root)
├── Trigger (e.g. Button or Modal.Trigger)
└── Modal.Backdrop (variant, isDismissable, isKeyboardDismissDisabled)
└── Modal.Container (placement, scroll, size)
└── Modal.Dialog
├── Modal.CloseTrigger (optional)
├── Modal.Header
│ ├── Modal.Icon (optional)
│ └── Modal.Heading
├── Modal.Body
└── Modal.Footer
```
## Summary
1. **Component Structure**: Must use compound components (`Modal.Container`, `Modal.Dialog`, etc.)
2. **Built-in Trigger**: Use `Modal.Trigger` for custom trigger elements or place a `Button` as a direct child of `Modal` -- no manual state wiring needed
3. **State Management**: `useDisclosure` replaced by `useOverlayState` hook; supports controlled/uncontrolled modes and a `state` prop on `Modal` root
4. **Props Moved**: Many props moved from `Modal` to `Modal.Backdrop` and `Modal.Container`
5. **Close Handler**: `onClose` callback replaced with `close` render prop on `Modal.Dialog`
6. **Close Button**: `hideCloseButton`/`closeButton` replaced with `Modal.CloseTrigger`
7. **Size**: Use `size` on `Modal.Container` (xs, sm, md, lg, cover, full); `radius` and `shadow` removed -- use Tailwind
8. **Backdrop**: `backdrop` → `variant` on `Modal.Backdrop` (values: `opaque`, `blur`, `transparent`)
9. **Scroll Renamed**: `scrollBehavior` → `scroll` (`normal` → `inside`)
10. **Motion Removed**: `motionProps` removed, animations handled differently
11. **New Components**: `Modal.Trigger`, `Modal.Icon`, `Modal.Heading`, `Modal.CloseTrigger`