Modal

Modal renders content in a centered, portal-mounted overlay. It is controlled by isOpen and supports the following:

  • Renders into a portal with role="dialog" / aria-modal="true", with aria-labelledby linked to the title.
  • Focus is trapped inside the dialog while open and released on close.
  • Body scroll is locked while open to keep the page behind the overlay still.
  • Closes on Escape or backdrop click — both call onClose. Omit onClose to render a non-dismissible modal (no close button, no escape).
  • Optional title, description, and footer slots, plus per-section className overrides for theming.

Import

import { Modal } from "h2o-library";

Usage

Basic

const [isOpen, setIsOpen] = useState(false);

<Button label="Open Modal" onClick={() => setIsOpen(true)} />

<Modal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="Confirm Action"
  description="Are you sure you want to proceed?"
>
  <p>Modal body content goes here.</p>
</Modal>

Use the footer slot to pin action buttons below the scrollable body. Place destructive actions on the right and a ghost Cancel on the left.

<Modal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="Delete Record"
  description="This action cannot be undone."
  footer={
    <>
      <Button variant="ghost" label="Cancel" onClick={() => setIsOpen(false)} />
      <Button variant="danger" label="Delete" onClick={handleDelete} />
    </>
  }
>
  <p>Are you sure you want to delete this record?</p>
</Modal>;

Sizes

<Modal size="sm" ...>  {/* 24rem — confirmations */}
<Modal size="md" ...>  {/* 32rem — default forms */}
<Modal size="lg" ...>  {/* 48rem — complex content */}

Non-dismissible

Omit onClose to render a modal that cannot be dismissed by the user — useful for blocking flows that must complete (e.g. a forced password reset). The close button is hidden and Escape / backdrop click do nothing.

<Modal isOpen={isOpen} title="Setting up your account">
  {/* user must complete the flow before continuing */}
</Modal>;

Playground

Loading playground…

Keyboard

KeyAction
EscapeCalls onClose when the modal is open (if onClose is provided).
Tab / Shift+TabFocus is trapped inside the dialog while open — cycle through focusable controls within the modal.

Props

PropTypeDefaultDescription
isOpen*booleanControls whether the modal is visible.
onClose() => voidundefinedCalled when the user dismisses the modal (Escape or backdrop click). Omit to render a non-dismissible modal — the close button is hidden.
titlestringundefinedTitle shown in the header. Also used as the accessible label via aria-labelledby.
descriptionstringundefinedSubtitle text shown below the title.
children*React.ReactNodeContent rendered in the scrollable modal body.
footerReact.ReactNodeundefinedContent pinned below the body (ideal for action buttons).
size"sm" | "md" | "lg""md"Maximum width: sm = 24rem, md = 32rem, lg = 48rem.
classNamestringundefinedAdditional class on the modal panel.
headerClassNamestringundefinedAdditional class on the header section.
titleContainerClassNamestringundefinedAdditional class on the title + description container.
titleClassNamestringundefinedAdditional class on the title element.
descriptionClassNamestringundefinedAdditional class on the description element.
contentClassNamestringundefinedAdditional class on the body section.
closeButtonClassNamestringundefinedAdditional class on the close button.

* Required

Design Guidelines

  • Use Modal for confirmations, short forms, and detail views that demand the user's full attention.
  • Use size="sm" for confirmations, size="md" for standard forms, size="lg" for data-heavy or multi-column layouts.
  • Always place action buttons in the footer prop — they stay pinned below the scrollable content and remain visible.
  • For destructive actions, place the danger button on the right and a ghost Cancel on the left.
  • If the content is long enough to require significant scrolling, prefer SidePanel so the user can still reference the page underneath.
  • Reach for PopOver for short contextual content that doesn't need to capture full attention.

Accessibility

  • Renders with role="dialog" and aria-modal="true", plus aria-labelledby linked to the title.
  • Focus is trapped inside the modal while open and returned to the originating element on close.
  • Body scroll is locked while the modal is open.
  • Escape and backdrop click call onClose. When onClose is omitted, the modal cannot be dismissed and the close button is hidden.