v0.5.1

Modal

Dialog overlay for confirmations, forms, and focused tasks, built on Radix UI.

Installation

npx paubha@latest add modal

Usage

import {
  Modal,
  ModalBody,
  ModalClose,
  ModalContent,
  ModalDescription,
  ModalFooter,
  ModalHeader,
  ModalTitle,
  ModalTrigger,
} from "@/components/ui/modal";

<Modal>
  <ModalTrigger asChild>
    <Button destructive>Delete item</Button>
  </ModalTrigger>
  <ModalContent>
    <ModalHeader>
      <ModalTitle>Delete item?</ModalTitle>
      <ModalClose />
    </ModalHeader>
    <ModalBody>
      <ModalDescription>This action cannot be undone.</ModalDescription>
    </ModalBody>
    <ModalFooter>
      <Button variant="secondary">Cancel</Button>
      <Button destructive>Confirm</Button>
    </ModalFooter>
  </ModalContent>
</Modal>

The close button sits inside ModalHeader as a flex sibling of the title (ModalTitle is flex-1, ModalClose is shrink-0). That's what the real spec shows, not the more common absolutely-positioned-corner-X pattern. The overlay backdrop is bg-bg-overlay (black at 60% in light mode, 70% in dark) and sits on the --z-overlay layer, with the panel above it on --z-modal. Both fade in over duration/normal (200ms) with ease-out; under prefers-reduced-motion they appear instantly.

Pick the smallest size that comfortably fits the task: sm, md (default), or lg.

API reference

Modal and ModalTrigger are direct re-exports of Radix Dialog's Root/Trigger. Compose ModalHeader, ModalTitle, ModalClose, ModalBody, ModalDescription, and ModalFooter inside ModalContent. Cancel/Confirm buttons are real <Button> instances you compose in, not baked into Modal itself.

ModalContent

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Max width: 400 / 560 / 720px. Use the smallest size that fits the task.

Accessibility

Accessibility

  • Role. role="dialog" with aria-modal="true", set explicitly on ModalContent rather than left to Radix's default, since the installed Radix version doesn't set it automatically (verified directly by inspecting the rendered DOM, not assumed).

  • Labelling. aria-labelledby points at ModalTitle, aria-describedby at ModalDescription, wired automatically by Radix.

  • Focus. Moves inside on open; Tab and Shift+Tab stay within the modal. Background content is inert while it's open.

  • Dismiss. Escape closes it and returns focus to the trigger, as does clicking ModalClose or the overlay. The icon-only close control is labelled "Close".

  • Motion. 200ms ease-out fade on open; instant when the user prefers reduced motion.