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", witharia-labelledbylinked 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
Escapeor backdrop click — both callonClose. OmitonCloseto render a non-dismissible modal (no close button, no escape). - Optional
title,description, andfooterslots, 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>
With Footer
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
| Key | Action |
|---|---|
Escape | Calls onClose when the modal is open (if onClose is provided). |
Tab / Shift+Tab | Focus is trapped inside the dialog while open — cycle through focusable controls within the modal. |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen* | boolean | — | Controls whether the modal is visible. |
onClose | () => void | undefined | Called when the user dismisses the modal (Escape or backdrop click). Omit to render a non-dismissible modal — the close button is hidden. |
title | string | undefined | Title shown in the header. Also used as the accessible label via aria-labelledby. |
description | string | undefined | Subtitle text shown below the title. |
children* | React.ReactNode | — | Content rendered in the scrollable modal body. |
footer | React.ReactNode | undefined | Content pinned below the body (ideal for action buttons). |
size | "sm" | "md" | "lg" | "md" | Maximum width: sm = 24rem, md = 32rem, lg = 48rem. |
className | string | undefined | Additional class on the modal panel. |
headerClassName | string | undefined | Additional class on the header section. |
titleContainerClassName | string | undefined | Additional class on the title + description container. |
titleClassName | string | undefined | Additional class on the title element. |
descriptionClassName | string | undefined | Additional class on the description element. |
contentClassName | string | undefined | Additional class on the body section. |
closeButtonClassName | string | undefined | Additional class on the close button. |
* Required
Design Guidelines
- Use
Modalfor 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
footerprop — 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
SidePanelso the user can still reference the page underneath. - Reach for
PopOverfor short contextual content that doesn't need to capture full attention.
Accessibility
- Renders with
role="dialog"andaria-modal="true", plusaria-labelledbylinked 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.
Escapeand backdrop click callonClose. WhenonCloseis omitted, the modal cannot be dismissed and the close button is hidden.