HeroUI

ModalUpdated

Dialog overlay for focused user interactions and important content

Import

import {Modal} from "@heroui/react";

Usage

"use client";

import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/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.Backdrop>
      <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.Backdrop>
  </Modal>
);

Placement

"use client";

import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";

Backdrop Variants

"use client";

import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";

Sizes

"use client";

import {Rocket} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";

Custom Backdrop

"use client";

import {Sparkles} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/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 {CircleInfo} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";

Close Methods

Using slot="close"

The simplest way to close a modal. Add slot="close" to any Button component within the modal. When clicked, it will automatically close the modal.

Using Dialog render props

Access the close method from the Dialog's render props. This gives you full control over when and how to close the modal, allowing you to add custom logic before closing.

"use client";

import {CircleCheck, CircleInfo} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";

Scroll Behavior

"use client";

import {Button, Label, Modal, Radio, RadioGroup} from "@heroui/react";
import {useState} from "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 {CircleCheck} from "@gravity-ui/icons";
import {Button, Modal, useOverlayState} from "@heroui/react";
import React from "react";

With Form

"use client";

import {Envelope} from "@gravity-ui/icons";
import {Button, Input, Label, Modal, Surface, TextField} from "@heroui/react";

Custom Trigger

"use client";

import {Gear} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";

Custom Animations

"use client";

import {ArrowUpFromLine, Sparkles} from "@gravity-ui/icons";
import {Button, Modal} from "@heroui/react";
import React from "react";

Custom Portal

Render modals inside a custom container instead of document.body

Apply transform: translateZ(0) to the container to create a new stacking context.

"use client";

import {Button, Modal} from "@heroui/react";
import {useCallback, useRef, useState} from "react";

Styling

Passing Tailwind CSS classes

import {Modal, Button} from "@heroui/react";

function CustomModal() {
  return (
    <Modal>
      <Button>Open Modal</Button>
      <Modal.Backdrop className="bg-black/80">
        <Modal.Container className="items-start pt-20">
          <Modal.Dialog className="bg-linear-to-br from-purple-500 to-pink-500 text-white">
            <Modal.CloseTrigger />
            <Modal.Header>
              <Modal.Heading>Custom Styled Modal</Modal.Heading>
            </Modal.Header>
            <Modal.Body>
              <p>This modal has custom styling applied via Tailwind classes</p>
            </Modal.Body>
            <Modal.Footer>
              <Button slot="close">Close</Button>
            </Modal.Footer>
          </Modal.Dialog>
        </Modal.Container>
      </Modal.Backdrop>
    </Modal>
  );
}

Customizing the component classes

To customize the Modal component classes, you can use the @layer components directive.


Learn more.

@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-visible or [data-focus-visible="true"] - Applied to trigger, dialog, and close button
  • Hover: :hover or [data-hovered="true"] - Applied to close button on hover
  • Active: :active or [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

PropTypeDefaultDescription
childrenReactNode-Trigger and container elements

Modal.Trigger

PropTypeDefaultDescription
childrenReactNode-Custom trigger content
classNamestring-CSS classes

Modal.Backdrop

PropTypeDefaultDescription
variant"opaque" | "blur" | "transparent""opaque"Backdrop overlay style
isDismissablebooleantrueClose on backdrop click
isKeyboardDismissDisabledbooleanfalseDisable ESC key to close
isOpenboolean-Controlled open state
onOpenChange(isOpen: boolean) => void-Open state change handler
classNamestring | (values) => string-Backdrop CSS classes
UNSTABLE_portalContainerHTMLElement-Custom portal container

Modal.Container

PropTypeDefaultDescription
placement"auto" | "center" | "top" | "bottom""auto"Modal position on screen
scroll"inside" | "outside""inside"Scroll behavior
size"xs" | "sm" | "md" | "lg" | "cover" | "full""md"Modal size variant
classNamestring | (values) => string-Container CSS classes

Modal.Dialog

PropTypeDefaultDescription
childrenReactNode | ({close}) => ReactNode-Content or render function
classNamestring | (values) => string-CSS classes
rolestring"dialog"ARIA role
aria-labelstring-Accessibility label
aria-labelledbystring-ID of label element
aria-describedbystring-ID of description element

Modal.Header

PropTypeDefaultDescription
childrenReactNode-Header content
classNamestring-CSS classes

Modal.Body

PropTypeDefaultDescription
childrenReactNode-Body content
classNamestring-CSS classes

Modal.Footer

PropTypeDefaultDescription
childrenReactNode-Footer content
classNamestring-CSS classes

Modal.CloseTrigger

PropTypeDefaultDescription
childrenReactNode-Custom close button
classNamestring | (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 directly

Accessibility

Implements WAI-ARIA Dialog pattern:

  • Focus trap: Focus locked within modal
  • Keyboard: ESC closes (when enabled), Tab cycles elements
  • Screen readers: Proper ARIA attributes
  • Scroll lock: Body scroll disabled when open

On this page