Registry

A ready-made shadcn/ui Modal built on Dialog, for Radix or Base UI.

The Modal component is available as a shadcn/ui registry component. This provides a pre-built Modal wrapper that integrates react-easy-modals with shadcn's Dialog component.

Installation

npx shadcn@latest add @react-easy-modals/modal

Usage

Simply replace Dialog with Modal in your imports:

- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
+ import { Modal, ModalContent, ModalHeader, ModalTitle } from "@/components/ui/modal"

No more DialogTrigger - just use modals.open() anywhere in your application:

- <Dialog>
-   <DialogTrigger asChild>
-     <Button>Open</Button>
-   </DialogTrigger>
-   <DialogContent>...</DialogContent>
- </Dialog>
+ const modals = useModals()
+ <Button onClick={() => modals.open(MyModal)}>Open</Button>

Example

Here's a simple confirm modal using the registry component:

result: undefined
import { ModalProps, ModalProvider, useModals } from "react-easy-modals";
import {
  Modal,
  ModalContent,
  ModalDescription,
  ModalFooter,
  ModalHeader,
  ModalTitle,
} from "@/components/ui/modal";
import { Button } from "@/components/ui/button";

type ConfirmModalReturnType = "confirm" | "cancel";

function ConfirmModal({ close }: ModalProps<ConfirmModalReturnType>) {
  return (
    <Modal>
      <ModalContent>
        <ModalHeader>
          <ModalTitle>Confirm Action</ModalTitle>
          <ModalDescription>
            "Are you sure you want to proceed?
          </ModalDescription>
        </ModalHeader>
        <ModalFooter>
          <Button variant="outline" onClick={() => close("cancel")}>
            Cancel
          </Button>
          <Button onClick={() => close("confirm")}>Confirm</Button>
        </ModalFooter>
      </ModalContent>
    </Modal>
  );
}

function App() {
  const modals = useModals();

  const handleClick = async () => {
    const result = await modals.open(ConfirmModal);
    if (result === "confirm") {
      // User confirmed
    }
  };

  return <Button onClick={handleClick}>Open Modal</Button>;
}

Nested Modals

For nested modals , the react-easy-modals nested API works perfectly with Base UI's nested dialogs.

To use nested modals, you must install shadcn with base-ui component library.

stack: []
import { ModalProps, ModalProvider, useModalAt, useModals } from "react-easy-modals";
import {
  Modal,
  ModalContent,
  ModalDescription,
  ModalFooter,
  ModalHeader,
  ModalTitle,
} from "@/components/ui/modal";
import { Button } from "@/components/ui/button";

function ChildModal({ close, open, title, nested }: ModalProps & { title: string }) {
  const modals = useModals();
  const root = useModalAt("root");
  const parent = useModalAt(-1);

  return (
    <Modal>
      <ModalContent>
        {nested}
        <ModalHeader>
          <ModalTitle>{title}</ModalTitle>
          <ModalDescription>
            Open another nested modal, close this one, or close the root.
          </ModalDescription>
        </ModalHeader>
        <ModalFooter className="flex flex-wrap gap-2">
          <Button
            variant="outline"
            onClick={() => open(ChildModal, { title: `Nested under ${title}` })}
          >
            Open nested
          </Button>
          <Button variant="outline" onClick={() => close()}>
            Close this
          </Button>
          <Button variant="outline" onClick={() => parent.close()}>
            Close parent
          </Button>
          <Button variant="outline" onClick={() => modals.open(ParentModal)}>
            Open new root
          </Button>
          <Button onClick={() => root.close()}>Close root</Button>
        </ModalFooter>
      </ModalContent>
    </Modal>
  );
}

function ParentModal({ open, close, nested }: ModalProps) {
  return (
    <Modal>
      <ModalContent>
        {nested}
        <ModalHeader>
          <ModalTitle>Parent Modal</ModalTitle>
          <ModalDescription>Open a nested modal inside this one.</ModalDescription>
        </ModalHeader>
        <ModalFooter>
          <Button variant="outline" onClick={() => close()}>
            Close
          </Button>
          <Button onClick={() => open(ChildModal, { title: "Nested!" })}>
            Open nested
          </Button>
        </ModalFooter>
      </ModalContent>
    </Modal>
  );
}

function App() {
  const modals = useModals();
  return <Button onClick={() => modals.open(ParentModal)}>Open</Button>;
}

export default function NestedExample() {
  return (
    <ModalProvider>
      <App />
    </ModalProvider>
  );
}

Nested dialog style

To enable the shadcn Base UI nested dialog style, make sure your DialogContent adds the nested transform and shadow classes inside the DialogPrimitive.Popup className={cn(...)} block.

<DialogPrimitive.Popup
  data-slot="dialog-content"
  className={cn(
    "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
+   "translate-y-[calc(1.20rem*var(--nested-dialogs,0))] scale-[calc(1-0.08*var(--nested-dialogs,0))] transition-[scale,translate]",
+   "data-nested:shadow-xl",
    className
  )}
/>

On this page