Modal

A focus-trapped dialog for critical actions, with scroll lock and full ARIA wiring.

Import#

import { Modal } from "astralis-ui";
// Modal.Trigger, .Content, .Header, .Body, .Footer,
// .Title, .Description, .Close, .CloseButton

Anatomy#

Trigger and Close wrap your own elements — usually Buttons — so the modal composes with anything focusable:

<Modal>
  <Modal.Trigger><Button>Open</Button></Modal.Trigger>
  <Modal.Content>
    <Modal.Header>
      <Modal.Title>Title</Modal.Title>
      <Modal.CloseButton />
    </Modal.Header>
    <Modal.Body>…</Modal.Body>
    <Modal.Footer>
      <Modal.Close><Button variant="text">Cancel</Button></Modal.Close>
    </Modal.Footer>
  </Modal.Content>
</Modal>

Sizes#

Controlled#

Own the state to keep the modal open during async work — pair with closeOnOverlayClick={false} so a stray click can't discard a pending save.

Props#

PropTypeDefaultDescription
open · defaultOpen · onOpenChangeboolean · boolean · (open: boolean) => void— · false · —Controlled / initial state, and the change callback.
size"sm" | "md" | "lg" | "xl" | "full""md"Panel width; full covers the viewport.
centeredbooleantrueVertically center the panel.
closeOnOverlayClickbooleantrueDismiss when the backdrop is clicked.
closeOnEscbooleantrueDismiss on Escape.

Parts#

PropTypeDefaultDescription
Modal.Trigger / Modal.Closechildren: ReactElementWrap a single element (e.g. a Button) to open / close the modal.
Modal.Contentchildren · classNameThe portalled, focus-trapped dialog panel.
Modal.Header / Body / Footerchildren · classNameLayout regions; Body scrolls when content overflows.
Modal.Title / Modal.Descriptionchildren · classNameWired to aria-labelledby / aria-describedby automatically.
Modal.CloseButtonclassNameThe corner ✕ button, placed inside the Header.

Keyboard#

KeyAction
EscClose (opt out with closeOnEsc={false})
Tab / Shift+TabCycle focus inside the dialog — it can't escape

On open, focus moves to the first focusable element inside; on close it returns to the trigger. With stacked overlays, Escape closes only the topmost layer at a time.

Accessibility#

  • Renders role="dialog" with aria-modal, portalled to the body; focus is trapped inside while open and page scroll is locked.
  • Modal.Title and Modal.Description wire aria-labelledby / aria-describedby automatically.
  • Escape and backdrop click dismiss by default; both are opt-out props.