@anupamsahoo/react-modal

Stacked & themeable modal for Tailwind v4

A small React modal component with stacking, variants, animations, and a Tailwind v4–friendly CSS API.

npm install @anupamsahoo/react-modalimport "@anupamsahoo/react-modal/styles.css"

Basic modal

A simple modal with header, body, and footer.

Variants, sizes & animations

Try different configurations of the modal.

Variant:
Size:
Animation:

Stacked modals

Open multiple modals. Only the top-most responds to ESC and overlay clicks.

useModalClose()

Close the modal from nested components without prop drilling.

API

<Modal /> props

PropTypeDefaultDescription
openbooleanControls whether the modal is visible.
onOpenChange(open: boolean) => voidCalled when the modal wants to change its open state (ESC, overlay click, close icon).
size"sm" | "md" | "lg" | "xl" | "full""lg"Controls max-width of the modal.
animation"scale" | "slide-up" | "slide-down" | "slide-left" | "slide-right" | "none""scale"Entry animation for the modal panel.
variant"default" | "danger" | "success" | "info""default"Controls border + header accent styling.
showCloseIconbooleantrueShow the floating close icon in the top-right.
disableOutsideClosebooleanfalseIf true, clicking the overlay does nothing.
disableEscClosebooleanfalseIf true, pressing ESC does not close the modal.
classNamestringExtra classes for the modal panel container.
childrenReact.ReactNodeUsually composed of ModalHeader / ModalBody / ModalFooter.

Other components

  • ModalHeader – header area, usually title + description. Props: className?, children.
  • ModalBody – scrollable body area. Props: className?, children.
  • ModalFooter – footer with actions. Props: className?, children.
  • useModalClose() – hook that returns a () => void function to close the modal from inside.

Code snippets

Basic usage (Next.js + Tailwind)

import * as React from "react";
import {
  Modal,
  ModalHeader,
  ModalBody,
  ModalFooter,
} from "@anupamsahoo/react-modal";
import "@anupamsahoo/react-modal/styles.css";

export function BasicExample() {
  const [open, setOpen] = React.useState(false);

  return (
    <>
      <button
        className="rounded-md px-3 py-2 text-sm bg-slate-900 text-white"
        onClick={() => setOpen(true)}
      >
        Open modal
      </button>

      <Modal open={open} onOpenChange={setOpen}>
        <ModalHeader>
          <h2 className="text-lg font-semibold">Simple modal</h2>
          <p className="text-sm text-slate-500">
            This is a basic example of the modal.
          </p>
        </ModalHeader>

        <ModalBody>
          <p className="text-sm">
            Put any content here – text, forms, lists, etc.
          </p>
        </ModalBody>

        <ModalFooter>
          <button
            className="rounded-md px-3 py-2 text-sm border border-slate-300"
            onClick={() => setOpen(false)}
          >
            Close
          </button>
          <button className="rounded-md px-3 py-2 text-sm bg-slate-900 text-white">
            Confirm
          </button>
        </ModalFooter>
      </Modal>
    </>
  );
}

Variants, sizes, animations

<Modal
  open={open}
  onOpenChange={setOpen}
  size="lg"               // "sm" | "md" | "lg" | "xl" | "full"
  variant="danger"        // "default" | "danger" | "success" | "info"
  animation="slide-up"    // "scale" | "slide-*" | "none"
>
  <ModalHeader>...</ModalHeader>
  <ModalBody>...</ModalBody>
  <ModalFooter>...</ModalFooter>
</Modal>

Stacked modals

const [outerOpen, setOuterOpen] = React.useState(false);
const [innerOpen, setInnerOpen] = React.useState(false);

<Modal open={outerOpen} onOpenChange={setOuterOpen}>
  ...
  <button onClick={() => setInnerOpen(true)}>Open inner</button>
</Modal>

<Modal open={innerOpen} onOpenChange={setInnerOpen}>
  ...
</Modal>

useModalClose hook

import { useModalClose } from "@anupamsahoo/react-modal";

function DeepChild() {
  const close = useModalClose();
  return (
    <button onClick={close}>
      Close modal
    </button>
  );
}

Tailwind v4 Setup (Recommended)

@anupamsahoo/react-modal is built for Tailwind v4. Follow these steps to ensure proper styling and dark mode support.

1. tailwind.css or app/globals.css

@import "tailwindcss";

/* Required for theme switching */
:root {
  color-scheme: light dark;
}

html {
  @apply bg-background text-foreground;
}

2. Recommended theme variables

:root {
  /* Base colors */
  --background: #ffffff;
  --foreground: #0f172a;
  --card: #ffffff;
  --card-foreground: #0f172a;
  --border: #e2e8f0;
  --muted: #f1f5f9;
  --muted-foreground: #64748b;

  /* Modal surface + text */
  --am-modal-bg: #ffffff;
  --am-modal-fg: #0f172a;
  --am-modal-border: #e2e8f0;

  /* Header + footer */
  --am-modal-header-bg: transparent;
  --am-modal-header-border: #e2e8f0;
  --am-modal-footer-border: #e2e8f0;

  /* Overlay (40% opacity and blur from component) */
  --am-modal-overlay-bg: rgba(0, 0, 0, 0.4);

  /* Semantic variants */
  --am-modal-danger-border: #ef4444;
  --am-modal-success-border: #22c55e;
  --am-modal-info-border: #0ea5e9;

  /* Close button */
  --am-modal-close-bg: #f1f5f9;
  --am-modal-close-bg-hover: #e2e8f0;
  --am-modal-close-fg: #0f172a;

  /* Radius sync */
  --am-modal-radius: 1rem;
}

.dark {
  /* Base colors */
  --background: #020617;
  --foreground: #f8fafc;
  --card: #020617;
  --card-foreground: #f8fafc;
  --border: #334155;
  --muted: #1e293b;
  --muted-foreground: #94a3b8;

  /* Modal surface + text */
  --am-modal-bg: #020617;
  --am-modal-fg: #f8fafc;
  --am-modal-border: #334155;

  /* Header + footer */
  --am-modal-header-bg: transparent;
  --am-modal-header-border: #334155;
  --am-modal-footer-border: #334155;

  /* Overlay (40% opacity + dark tone) */
  --am-modal-overlay-bg: rgba(2, 6, 23, 0.4);

  /* Semantic variants */
  --am-modal-danger-border: #f43f5e;
  --am-modal-success-border: #4ade80;
  --am-modal-info-border: #38bdf8;

  /* Close button */
  --am-modal-close-bg: rgba(255, 255, 255, 0.08);
  --am-modal-close-bg-hover: rgba(255, 255, 255, 0.15);
  --am-modal-close-fg: #f8fafc;

  /* Radius sync */
  --am-modal-radius: 1rem;
}

3. Enable dark mode toggle

// ThemeToggle.tsx
"use client";

export default function ThemeToggle() {
  const toggle = () => {
    document.documentElement.classList.toggle("dark");
  };

  return (
    <button
      onClick={toggle}
      className="fixed right-6 top-6 z-50 rounded-md 
        bg-slate-900 text-white px-3 py-2 text-xs
        dark:bg-slate-100 dark:text-slate-900"
    >
      Toggle theme
    </button>
  );
}
That’s it — your modal now automatically follows Tailwind v4 + dark/light theme without any extra config.