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>
);
}