Dropdown Menu

A dropdown menu to display actions.

import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuPortal,
  DropdownMenuPositioner,
  DropdownMenuPopup,
  DropdownMenuItem,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu/dropdown-menu";
import { Button } from "@/components/ui/button/button";
import { Edit, Copy, Archive, Trash2, ChevronDown } from "lucide-react";
import styles from "./dropdown-menu-demo.module.css";

export default function DropdownMenuDemo() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        render={
          <Button variant="outline">
            <div className={styles.triggerContent}>
              <span> Actions </span>
              <ChevronDown size={16} className={styles.chevronIcon} />
            </div>
          </Button>
        }
      />
      <DropdownMenuPortal>
        <DropdownMenuPositioner sideOffset={8}>
          <DropdownMenuPopup>
            <DropdownMenuItem icon={<Edit size={16} />}>Edit</DropdownMenuItem>

            <DropdownMenuSeparator />
            <DropdownMenuItem icon={<Copy size={16} />}>Duplicate</DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuItem icon={<Archive size={16} />}>Archive</DropdownMenuItem>
            <DropdownMenuItem icon={<Trash2 size={16} />}>Delete</DropdownMenuItem>
          </DropdownMenuPopup>
        </DropdownMenuPositioner>
      </DropdownMenuPortal>
    </DropdownMenu>
  );
}

Installation

npx shadcn@latest add https://roiui.com/r/dropdown-menu.json

Anatomy

anatomy
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuPortal,
DropdownMenuPositioner,
DropdownMenuPopup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuArrow,
DropdownMenuSubmenuRoot,
DropdownMenuSubmenuTrigger,
} from '../ui/dropdown-menu'

<DropdownMenu>
  <DropdownMenuTrigger />
  <DropdownMenuPortal>
      <DropdownMenuPositioner>
          <DropdownMenuPopup>
              <DropdownMenuArrow />
              <DropdownMenuItem />
              <DropdownMenuSeparator />
              <DropdownMenuSubmenuRoot>
                  <DropdownMenuSubmenuTrigger />
              </DropdownMenuSubmenuRoot>
          </DropdownMenuPopup>
      </DropdownMenuPositioner>
  </DropdownMenuPortal>
</DropdownMenu>

Examples

With Submenu

dropdown-menu-submenu.tsx
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuPortal,
  DropdownMenuPositioner,
  DropdownMenuPopup,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuSubmenuRoot,
  DropdownMenuSubmenuTrigger,
} from "@/components/ui/dropdown-menu/dropdown-menu";
import { Button } from "@/components/ui/button/button";
import { ChevronRight } from "lucide-react";

export default function DropdownMenuSubmenu() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline">Open Menu</Button>} />
      <DropdownMenuPortal>
        <DropdownMenuPositioner>
          <DropdownMenuPopup>
            <DropdownMenuItem>Profile</DropdownMenuItem>
            <DropdownMenuItem>Settings</DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuSubmenuRoot>
              <DropdownMenuSubmenuTrigger style={{ justifyContent: "space-between" }}>
                More Options
                <ChevronRight size={16} />
              </DropdownMenuSubmenuTrigger>
              <DropdownMenuPortal>
                <DropdownMenuPositioner side="right" align="start" sideOffset={8}>
                  <DropdownMenuPopup>
                    <DropdownMenuItem>Export</DropdownMenuItem>
                    <DropdownMenuItem>Import</DropdownMenuItem>
                    <DropdownMenuItem>Share</DropdownMenuItem>
                  </DropdownMenuPopup>
                </DropdownMenuPositioner>
              </DropdownMenuPortal>
            </DropdownMenuSubmenuRoot>
            <DropdownMenuSeparator />
            <DropdownMenuItem>Logout</DropdownMenuItem>
          </DropdownMenuPopup>
        </DropdownMenuPositioner>
      </DropdownMenuPortal>
    </DropdownMenu>
  );
}

With Motion

"use client";

import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuPortal,
  DropdownMenuPositioner,
  DropdownMenuPopup,
  DropdownMenuItem,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu/dropdown-menu";
import { motion, AnimatePresence } from "motion/react";
import { useState } from "react";
import { ChevronDown, User, Settings, Users, CreditCard, LogOut } from "lucide-react";
import { Button } from "@/components/ui/button/button";
import styles from "./dropdown-menu-motion.module.css";

const menuItems = [
  { label: "Profile", icon: User },
  { label: "Settings", icon: Settings },
  { label: "Team", icon: Users },
  { label: "Billing", icon: CreditCard },
];

const popupVariants = {
  hidden: { opacity: 0, scale: 0.8, originX: -0.1, originY: -0.1 },
  visible: { opacity: 1, scale: 1 },
  exit: { opacity: 0, scale: 0.8, originX: -0.1, originY: -0.1 },
};

const popupTransition = {
  opacity: { duration: 0.15 },
  scale: { type: "spring" as const, duration: 0.4, bounce: 0.5 },
};

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

  return (
    <DropdownMenu open={open} onOpenChange={setOpen}>
      <DropdownMenuTrigger
        render={
          <Button variant="outline">
            <div className={styles.triggerContent}>
              <span> Open Menu </span>
              <ChevronDown size={16} className={styles.chevronIcon} />
            </div>
          </Button>
        }
      ></DropdownMenuTrigger>

      <DropdownMenuPortal>
        <DropdownMenuPositioner>
          <AnimatePresence>
            {open && (
              <DropdownMenuPopup
                render={
                  <motion.div
                    variants={popupVariants}
                    initial="hidden"
                    animate="visible"
                    exit="exit"
                    transition={popupTransition}
                  />
                }
              >
                {menuItems.map((item) => (
                  <motion.div key={item.label}>
                    <DropdownMenuItem render={<Button variant="ghost" className={styles.menuItemButton} />}>
                      <div className={styles.menuItemContent}>
                        <item.icon size={16} />
                        {item.label}
                      </div>
                    </DropdownMenuItem>
                  </motion.div>
                ))}

                <motion.div>
                  <DropdownMenuSeparator className={styles.separator} />
                </motion.div>

                <DropdownMenuItem render={<Button variant="ghost" className={styles.menuItemButton} />}>
                  <div className={styles.menuItemContent}>
                    <LogOut size={16} />
                    Logout
                  </div>
                </DropdownMenuItem>
              </DropdownMenuPopup>
            )}
          </AnimatePresence>
        </DropdownMenuPositioner>
      </DropdownMenuPortal>
    </DropdownMenu>
  );
}