Menu
NavigationDropdown menu with items and separators.
Preview
Usage
example.jsx
import { Menu, MenuItem, MenuSeparator, MenuLabel } from "@/components/ui/menu";
export default function Example() {
return <Menu />;
}Source Code
Copy this file into components/ui/menu.jsx in your project.
menu.jsx
"use client";
import { forwardRef, useState, useRef, useEffect } from "react";
import { cn } from "@/lib/utils";
const Menu = forwardRef(({ className, trigger, children, align = "start", ...props }, ref) => {
const [open, setOpen] = useState(false);
const menuRef = useRef(null);
useEffect(() => {
const handler = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) setOpen(false); };
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
return (
<div ref={(el) => { menuRef.current = el; if (typeof ref === "function") ref(el); else if (ref) ref.current = el; }} className={cn("relative inline-block", className)} {...props}>
<div onClick={() => setOpen(!open)}>{trigger}</div>
{open && (
<div className={cn("absolute z-50 mt-1 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95",
align === "end" ? "right-0" : "left-0"
)}>
{children}
</div>
)}
</div>
);
});
Menu.displayName = "Menu";
const MenuItem = forwardRef(({ className, disabled, ...props }, ref) => (
<button ref={ref} disabled={disabled}
className={cn("relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground",
disabled && "pointer-events-none opacity-50", className
)}
{...props}
/>
));
MenuItem.displayName = "MenuItem";
const MenuSeparator = forwardRef(({ className, ...props }, ref) => (
<div ref={ref} className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} />
));
MenuSeparator.displayName = "MenuSeparator";
const MenuLabel = forwardRef(({ className, ...props }, ref) => (
<div ref={ref} className={cn("px-2 py-1.5 text-xs font-semibold text-muted-foreground", className)} {...props} />
));
MenuLabel.displayName = "MenuLabel";
export { Menu, MenuItem, MenuSeparator, MenuLabel };
Quick Install
Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.
npm install clsx tailwind-merge