Menubar
A visually persistent menu common in desktop applications.
Demo
Dependencies
lucide-reactclsxtailwind-merge
Props
This is a base primitive. It accepts standard HTML attributes plus className for styling via cn().
Source
"use client";
import { useState, useRef, useEffect, createContext, useContext } from "react";
import { cn } from "@/lib/utils";
import { ChevronRight } from "lucide-react";
interface MenubarCtxValue {
openMenu: string | null;
setOpenMenu: (v: string | null) => void;
}
const MenubarCtx = createContext<MenubarCtxValue>({
openMenu: null,
setOpenMenu: () => {},
});
export function Menubar({
children,
className,
}: {
children?: React.ReactNode;
className?: string;
}) {
const [openMenu, setOpenMenu] = useState<string | null>(null);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const h = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node))
setOpenMenu(null);
};
document.addEventListener("mousedown", h);
return () => document.removeEventListener("mousedown", h);
}, []);
return (
<MenubarCtx.Provider value={{ openMenu, setOpenMenu }}>
<div
ref={ref}
className={cn(
"flex items-center gap-1 rounded-lg border border-gray-200 bg-white p-1",
className,
)}
>
{children}
</div>
</MenubarCtx.Provider>
);
}
export function MenubarMenu({
label,
children,
}: {
label: string;
children?: React.ReactNode;
}) {
const { openMenu, setOpenMenu } = useContext(MenubarCtx);
const isOpen = openMenu === label;
return (
<div className="relative">
<button
onClick={() => setOpenMenu(isOpen ? null : label)}
className={cn(
"rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
isOpen
? "bg-gray-100 text-gray-900"
: "text-gray-600 hover:bg-gray-50",
)}
>
{label}
</button>
{isOpen && (
<div className="absolute left-0 top-full z-50 mt-1 min-w-[10rem] rounded-lg border border-gray-200 bg-white py-1 shadow-sm">
{children}
</div>
)}
</div>
);
}
export function MenubarItem({
children,
onClick,
}: {
children?: React.ReactNode;
onClick?: () => void;
}) {
const { setOpenMenu } = useContext(MenubarCtx);
return (
<button
onClick={() => {
onClick?.();
setOpenMenu(null);
}}
className="flex w-full items-center px-3 py-1.5 text-left text-sm text-gray-700 hover:bg-gray-100"
>
{children}
</button>
);
}
export function MenubarSeparator() {
return <div className="my-1 border-t border-gray-200" />;
}
CLI
npx genui add menubar