Skip to content

Dialog

Modal dialog with focus trap, scroll lock, restore-on-close, and compound header/body/footer API.

Overlays & Feedback·Available inReactVueAngular·View as Markdown

Preview

tsx

Switch the framework picker (top-right of the panel) to render the same demo live in React, Vue, or Angular — same class names, ARIA, and visual output across all three.

Installation

Sisyphos UI ships unified packages for React, Vue, and Angular. Pick the one that matches your stack — every framework exports the same component classes, ARIA semantics, and CSS tokens.

$ pnpm add @sisyphos-ui/react

Then import the bundled stylesheet once at app entry: import "@sisyphos-ui/react/styles.css";

Usage

Idiomatic usage in each supported framework
import { useState } from "react";
import { Button, Dialog } from "@sisyphos-ui/react";

export function ConfirmDelete() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Button onClick={() => setOpen(true)} color="error">Delete</Button>
      <Dialog open={open} onOpenChange={setOpen} size="md">
        <Dialog.Header>
          <Dialog.Title>Delete this item?</Dialog.Title>
          <Dialog.Close />
        </Dialog.Header>
        <Dialog.Body>
          <Dialog.Description>This action cannot be undone.</Dialog.Description>
        </Dialog.Body>
        <Dialog.Footer>
          <Button variant="outlined" onClick={() => setOpen(false)}>Cancel</Button>
          <Button color="error" onClick={() => setOpen(false)}>Delete</Button>
        </Dialog.Footer>
      </Dialog>
    </>
  );
}

Anatomy

The Dialog is a compound component. Compose the parts that match your use case.

Anatomy
<Dialog.Root>
  <Dialog.Trigger />// Button that opens the dialog.
  <Dialog.Overlay />// Scrim behind the dialog; closes on click.
  <Dialog.Content />// Focus-trapped panel.required
  <Dialog.Header />// Optional titled header slot.
  <Dialog.Body />// Scrollable content region.
  <Dialog.Footer />// Action buttons row.
  <Dialog.Close />// Closes the dialog and restores focus.
</Dialog.Root>

Sizes

`size` caps `max-width`. Use `full` to fill the viewport.

tsx

With form & initialFocus

`initialFocus` accepts a ref — the dialog focuses that element on open so keyboard users start on the first field.

tsx

Destructive confirm

Pair a dialog with a destructive action and a cautious copy — default focus stays on Cancel.

tsx

Auto-rendered close button

Set `showCloseButton` to get a close affordance in the top-right without composing `<Dialog.Close />` manually. `closeButtonLabel` overrides the aria-label.

tsx

No backdrop

Set `backdrop={false}` + `closeOnBackdropClick={false}` for contextual, in-place sheets.

tsx

API

PropTypeDefaultDescription
open*booleanWhether the dialog is open. Always controlled.
onOpenChange*(open: boolean) => voidCalled with the next open state when the dialog requests to close.
size"sm" | "md" | "lg" | "xl" | "full""md"Panel width preset; `full` covers the viewport.
closeOnBackdropClickbooleantrueClose when the backdrop is clicked.
closeOnEscapebooleantrueClose when Escape is pressed.
backdropbooleantrueRender the scrim behind the panel; disable for transparent dialogs.
showCloseButtonbooleanfalseRender a floating close button in the top-right; for custom placement compose `Dialog.Close` instead.
closeButtonLabelstring"Close"`aria-label` for the auto-rendered close button.
initialFocusRefObject<HTMLElement>Element that receives focus on open; defaults to the first focusable child.

The full API including refs, ARIA attributes, and HTML passthroughs lives in the package README on npm.

Accessibility

Adheres to the Dialog (Modal) WAI-ARIA design pattern.

  • The panel renders role="dialog" with aria-modal="true"; aria-labelledby is wired automatically to Dialog.Title and aria-describedby to Dialog.Description.
  • Focus is trapped inside the open dialog (useFocusTrap) and restored to the previously focused element on close.
  • Body scroll is locked while open (useScrollLock), so background content can't be scrolled or interacted with.
  • The auto-rendered close button carries a configurable aria-label (closeButtonLabel, defaults to "Close").

Keyboard interactions

KeyAction
TabMoves focus to the next focusable element within the dialog. Focus is trapped.
Shift+TabMoves focus to the previous focusable element.
EscCloses the dialog and returns focus to the trigger.
Need more?View on npm →
Was this page helpful?