Modal

Modal is a centered overlay dialog that dims the background and focuses the user on a specific task. Use it for confirmations, forms, and any interaction that should block the underlying page until dismissed.


Installation

npm install @libretexts/davis-react

Basic Usage

import { Modal, Button } from '@libretexts/davis-react';
import { useState } from 'react';

export default function Example() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Open Modal</Button>
      <Modal open={open} onClose={() => setOpen(false)}>
        <Modal.Header>
          <Modal.Title>Modal Title</Modal.Title>
          <Modal.Close />
        </Modal.Header>
        <Modal.Body>
          <p>Modal content goes here.</p>
        </Modal.Body>
        <Modal.Footer>
          <Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
          <Button onClick={() => setOpen(false)}>Confirm</Button>
        </Modal.Footer>
      </Modal>
    </>
  );
}

Sizes

Modal supports five size variants that control the maximum width of the panel.

<Modal open={open} onClose={() => setOpen(false)} size="sm">
  {/* Small — max-w-sm */}
</Modal>

<Modal open={open} onClose={() => setOpen(false)} size="md">
  {/* Medium (default) — max-w-md */}
</Modal>

<Modal open={open} onClose={() => setOpen(false)} size="lg">
  {/* Large — max-w-lg */}
</Modal>

<Modal open={open} onClose={() => setOpen(false)} size="xl">
  {/* Extra large — max-w-xl */}
</Modal>

<Modal open={open} onClose={() => setOpen(false)} size="full">
  {/* Full width — max-w-full */}
</Modal>

Confirmation Dialog

Use a small or medium modal with a destructive action button for confirmations.

import { Modal, Button } from '@libretexts/davis-react';
import { useState } from 'react';

export default function ConfirmDelete() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button variant="destructive" onClick={() => setOpen(true)}>
        Delete Item
      </Button>
      <Modal open={open} onClose={() => setOpen(false)} size="sm">
        <Modal.Header>
          <Modal.Title>Delete Item</Modal.Title>
          <Modal.Close />
        </Modal.Header>
        <Modal.Body>
          <Modal.Description>
            Are you sure you want to delete this item? This action cannot be undone.
          </Modal.Description>
        </Modal.Body>
        <Modal.Footer>
          <Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
          <Button variant="destructive" onClick={() => setOpen(false)}>Delete</Button>
        </Modal.Footer>
      </Modal>
    </>
  );
}

With Form

Modals work well for short forms that require focused input.

import { Modal, Button, Input } from '@libretexts/davis-react';
import { useState } from 'react';

export default function FormModal() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Edit Profile</Button>
      <Modal open={open} onClose={() => setOpen(false)} size="lg">
        <Modal.Header>
          <Modal.Title>Edit Profile</Modal.Title>
          <Modal.Close />
        </Modal.Header>
        <Modal.Body>
          <form className="space-y-4">
            <Input label="Name" placeholder="Enter your name" />
            <Input label="Email" type="email" placeholder="Enter your email" />
          </form>
        </Modal.Body>
        <Modal.Footer>
          <Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
          <Button onClick={() => setOpen(false)}>Save Changes</Button>
        </Modal.Footer>
      </Modal>
    </>
  );
}

Sub-components

Modal uses a compound component pattern. Compose it with the following sub-components:

ComponentDescription
Modal.HeaderContainer for the title and close button. Renders a bordered header row.
Modal.TitleThe dialog's accessible title. Rendered as an h2 via HeadlessUI's DialogTitle.
Modal.DescriptionOptional accessible description text for the dialog.
Modal.BodyScrollable content area between the header and footer.
Modal.FooterAction bar at the bottom of the modal, typically containing buttons.
Modal.CloseClose button that renders an X icon and calls onClose.

Props

PropTypeDefaultDescription
openbooleanControls whether the modal is visible
onClose(value: boolean) => voidCalled when the modal should close (backdrop click, Escape key, or close button)
size"sm" | "md" | "lg" | "xl" | "full""md"Maximum width of the modal panel
classNamestringAdditional CSS classes for the panel
childrenReactNodeModal content, typically composed of sub-components
PropTypeDefaultDescription
childrenReactNodeHeader content, typically Modal.Title and Modal.Close
classNamestringAdditional CSS classes
PropTypeDefaultDescription
childrenReactNodeTitle text
classNamestringAdditional CSS classes
PropTypeDefaultDescription
childrenReactNodeDescription text
classNamestringAdditional CSS classes
PropTypeDefaultDescription
childrenReactNodeBody content
classNamestringAdditional CSS classes
PropTypeDefaultDescription
childrenReactNodeFooter content, typically action buttons
classNamestringAdditional CSS classes
PropTypeDefaultDescription
aria-labelstring"Close modal"Accessible label for the close button
classNamestringAdditional CSS classes

When to use

  • Modal — Use for focused interactions that require a decision or input before the user can return to the page: confirmations, short forms, alerts, or critical information.
  • Drawer — Use for contextual side panels (filters, detail views, settings) where the user may want to reference the underlying page.
  • Dialog — Use as a lower-level primitive when you need full control over dialog behavior without the opinionated compound-component layout.

Accessibility

Modal is built on Headless UI's Dialog primitive. Focus is trapped inside the modal when open, and pressing Escape closes it. Focus returns to the trigger element on close. Modal.Title and Modal.Description are automatically associated with the dialog via aria-labelledby and aria-describedby.