Dialog

A modal dialog component for displaying content in an overlay.

import { Button } from "@/components/ui/button/button";
import {
  Dialog,
  DialogClose,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogOverlay,
  DialogPopup,
  DialogPortal,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog/dialog";
import { Input } from "@/components/ui/input/input";
import styles from "./dialog-demo.module.css";

export default function DialogDemo() {
  return (
    <Dialog>
      <DialogTrigger render={<Button>Open Dialog</Button>} />
      <DialogPortal>
        <DialogOverlay />
        <DialogPopup className={styles.popup}>
          <DialogHeader>
            <DialogTitle>Edit Profile</DialogTitle>
            <DialogDescription className={styles.description}>Make changes to your profile here.</DialogDescription>
          </DialogHeader>
          <div className={styles.form}>
            <div className={styles.fieldGroup}>
              <label className={styles.label} htmlFor="name">
                Name
              </label>
              <Input defaultValue="John Doe" id="name" />
            </div>
            <div className={styles.fieldGroup}>
              <label className={styles.label} htmlFor="email">
                Email
              </label>
              <Input defaultValue="john@example.com" id="email" type="email" />
            </div>
          </div>
          <DialogFooter className={styles.footer}>
            <DialogClose render={<Button variant="outline">Cancel</Button>} />
            <Button>Save Changes</Button>
          </DialogFooter>
        </DialogPopup>
      </DialogPortal>
    </Dialog>
  );
}

npx shadcn@latest add @roiui/dialog
npx shadcn@latest add @roiui/dialog-tailwind

anatomy
<Dialog>
  <DialogTrigger />
  <DialogContent>
      <DialogHeader>
          <DialogTitle />
          <DialogDescription />
      </DialogHeader>
      <DialogFooter>
          <DialogClose />
      </DialogFooter>
  </DialogContent>
</Dialog>

"use client";

import { X } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button/button";
import {
  Dialog,
  DialogClose,
  DialogDescription,
  DialogHeader,
  DialogOverlay,
  DialogPopup,
  DialogPortal,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog/dialog";
import styles from "./dialog-sheet.module.css";

type Side = "left" | "right";

export default function DialogSheet() {
  const [side, setSide] = useState<Side>("right");

  return (
    <div className={styles.buttons}>
      <Dialog>
        <DialogTrigger
          render={
            <Button onClick={() => setSide("left")} variant="outline">
              Open Left
            </Button>
          }
        />
        <DialogTrigger
          render={
            <Button onClick={() => setSide("right")} variant="outline">
              Open Right
            </Button>
          }
        />
        <DialogPortal>
          <DialogOverlay />
          <DialogPopup className={styles.popup} data-side={side}>
            <DialogClose className={styles.closeButton} render={<button type="button" />}>
              <X size={16} />
            </DialogClose>
            <DialogHeader className={styles.header}>
              <DialogTitle>Sheet Panel</DialogTitle>
              <DialogDescription>This dialog slides in from the {side} side of the screen.</DialogDescription>
            </DialogHeader>
            <div className={styles.content}>
              <p>
                Sheet dialogs are useful for navigation menus, settings panels, or any content that benefits from a
                slide-in interaction.
              </p>
            </div>
            <div className={styles.footer}>
              <DialogClose render={<Button variant="outline">Close</Button>} />
              <Button>Save</Button>
            </div>
          </DialogPopup>
        </DialogPortal>
      </Dialog>
    </div>
  );
}

"use client";

import { Dialog } from "@base-ui/react/dialog";
import { AnimatePresence, motion, type Variants } from "motion/react";
import { useRef, useState } from "react";
import styles from "./dialog-motion.module.css";

const contentVariants: Variants = {
  hidden: {
    opacity: 0,
    x: 30,
    y: 30,
    scale: 0.7,
    filter: "blur(4px)",
    transformOrigin: "right center",
  },
  visible: {
    opacity: 1,
    transformOrigin: "right center",
    scale: 1,
    filter: "blur(0px)",
    x: 0,
    y: 0,
    transition: {
      duration: 0.38,
      delay: 0.12,
      type: "spring",
      bounce: 0,
      ease: "anticipate",
    },
  },
  exit: {
    opacity: 0,
    filter: "blur(4px)",

    scale: 0.97,
    transition: {
      duration: 0.5,
      type: "spring",
    },
  },
};

const closeButtonVariants: Variants = {
  hidden: {
    opacity: 0,
    filter: "blur(4px)",
    scale: 0.97,
  },
  visible: {
    opacity: 1,
    scale: 1,
    filter: "blur(0px)",
    rotate: 0,
    originX: -12,
    transition: {
      duration: 0.38,
      delay: 0.12,
      type: "spring",
      bounce: 0,
      ease: "anticipate",
    },
  },
  exit: {
    opacity: 0,

    filter: "blur(4px)",
    scale: 0.97,
    transition: {
      duration: 0.5,
      type: "spring",
    },
  },
};

export default function DialogFramerMotion() {
  const [open, setOpen] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);

  return (
    <Dialog.Root onOpenChange={setOpen} open={open}>
      <div className={styles.triggerWrapper}>
        <motion.div className={styles.morphWrapper} layoutId="wrapper" style={{ borderRadius: 8 }} />
        <Dialog.Trigger className={styles.trigger} render={<motion.button layoutId="button" />}>
          Upgrade
        </Dialog.Trigger>
      </div>

      <AnimatePresence>
        {open && (
          <Dialog.Backdrop
            className={styles.overlay}
            hidden={undefined}
            key="overlay"
            render={
              <motion.div
                animate={{
                  opacity: 1,
                }}
                exit={{ opacity: 0 }}
                initial={{ opacity: 0 }}
              />
            }
          />
        )}
      </AnimatePresence>
      <AnimatePresence>
        {open && (
          <Dialog.Portal container={containerRef} keepMounted>
            <div className={styles.popupWrapper}>
              <motion.div className={styles.popupMorphWrapper} layoutId="wrapper" style={{ borderRadius: 12 }} />
              <Dialog.Popup className={styles.popup} hidden={undefined}>
                <div className={styles.popupContent}>
                  <Dialog.Title
                    className={styles.title}
                    render={<motion.span animate="visible" exit="exit" initial="hidden" variants={contentVariants} />}
                  >
                    Plan Plus
                  </Dialog.Title>
                  <Dialog.Description
                    className={styles.description}
                    render={<motion.p animate="visible" exit="exit" initial="hidden" variants={contentVariants} />}
                  >
                    Upgrade your plan for full access.
                  </Dialog.Description>

                  <div className={styles.actions}>
                    <Dialog.Close
                      className={styles.closeButton}
                      render={
                        <motion.button
                          animate="visible"
                          exit="exit"
                          initial="hidden"
                          style={{ originX: -20, originY: -20 }}
                          variants={closeButtonVariants}
                        />
                      }
                    >
                      Close
                    </Dialog.Close>
                    <motion.button className={styles.actionButton} layoutId="button">
                      Upgrade
                    </motion.button>
                  </div>
                </div>
              </Dialog.Popup>
            </div>
          </Dialog.Portal>
        )}
      </AnimatePresence>
    </Dialog.Root>
  );
}