# 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`