Use Menubar for application commands such as File and View, not as the default navigation of a marketing site. Each MenubarMenu owns a trigger and content. Use Navigation Menu for page navigation.
Live preview
"use client";
import { useState } from "react";
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar";
export default function MenubarDemo() {
const [showPreviews, setShowPreviews] = useState(true);
const [showNotes, setShowNotes] = useState(true);
const [compactNavigation, setCompactNavigation] = useState(false);
const [environment, setEnvironment] = useState("preview");
return (
<div className="w-full overflow-x-auto p-1">
<Menubar className="w-max min-w-full">
<MenubarMenu>
<MenubarTrigger>Project</MenubarTrigger>
<MenubarContent>
<MenubarItem>
New component <MenubarShortcut>Ctrl+N</MenubarShortcut>
</MenubarItem>
<MenubarItem>Open registry</MenubarItem>
<MenubarSub>
<MenubarSubTrigger>Export</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Registry JSON</MenubarItem>
<MenubarItem>Theme CSS</MenubarItem>
<MenubarItem>Documentation snapshot</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem disabled>Publish release</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem
checked={showPreviews}
onCheckedChange={(checked) => setShowPreviews(checked === true)}
>
Show component previews
</MenubarCheckboxItem>
<MenubarCheckboxItem
checked={showNotes}
onCheckedChange={(checked) => setShowNotes(checked === true)}
>
Show accessibility notes
</MenubarCheckboxItem>
<MenubarCheckboxItem
checked={compactNavigation}
onCheckedChange={(checked) => setCompactNavigation(checked === true)}
>
Compact navigation
</MenubarCheckboxItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Environment</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup value={environment} onValueChange={setEnvironment}>
<MenubarRadioItem value="local">Local</MenubarRadioItem>
<MenubarRadioItem value="preview">Preview</MenubarRadioItem>
<MenubarRadioItem value="production">Production</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
</div>
);
}
#Installation
Complete the base setup. For manual installation:
First time? Install the shared base once.
npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/menubar.jsonnpm install @base-ui/react@^1.8.0Also install Dropdown Menu, then create components/ui/menubar.tsx:
"use client";
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar";
import type * as React from "react";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";
function Menubar({ className, ...props }: MenubarPrimitive.Props) {
return (
<MenubarPrimitive
data-slot="menubar"
{...props}
className={(state) =>
cn(
"flex h-11 items-center gap-1 rounded-base border-2 border-border bg-main p-1 font-base",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function MenubarMenu(props: React.ComponentProps<typeof DropdownMenu>) {
return <DropdownMenu data-slot="menubar-menu" {...props} />;
}
function MenubarGroup(props: React.ComponentProps<typeof DropdownMenuGroup>) {
return <DropdownMenuGroup data-slot="menubar-group" {...props} />;
}
function MenubarPortal(props: React.ComponentProps<typeof DropdownMenuPortal>) {
return <DropdownMenuPortal data-slot="menubar-portal" {...props} />;
}
function MenubarTrigger({ className, ...props }: React.ComponentProps<typeof DropdownMenuTrigger>) {
return (
<DropdownMenuTrigger
data-slot="menubar-trigger"
{...props}
className={(state) =>
cn(
"flex cursor-default items-center rounded-base border-2 border-transparent px-3 py-1.5 text-sm font-heading text-main-foreground outline-none select-none hover:border-current focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-4 focus-visible:outline-current data-popup-open:outline-2 data-popup-open:outline-solid data-popup-open:-outline-offset-4 data-popup-open:outline-current",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function MenubarContent({
className,
align = "start",
alignOffset = -4,
sideOffset = 8,
...props
}: React.ComponentProps<typeof DropdownMenuContent>) {
return (
<DropdownMenuContent
data-slot="menubar-content"
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
{...props}
className={(state) =>
cn(
"min-w-[12rem] rounded-base border-2 border-border bg-main p-1 font-base text-main-foreground duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function MenubarItem({ className, ...props }: React.ComponentProps<typeof DropdownMenuItem>) {
return (
<DropdownMenuItem
data-slot="menubar-item"
{...props}
className={(state) =>
cn(
"group/menubar-item gap-2 rounded-base border-2 border-transparent px-2 py-1.5 text-sm font-base data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function MenubarCheckboxItem({
className,
...props
}: React.ComponentProps<typeof DropdownMenuCheckboxItem>) {
return (
<DropdownMenuCheckboxItem
data-slot="menubar-checkbox-item"
{...props}
className={(state) =>
cn(
"relative flex cursor-default items-center gap-2 rounded-base border-2 border-transparent py-1.5 pr-2 pl-8 text-sm font-base transition-colors select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function MenubarRadioGroup(props: React.ComponentProps<typeof DropdownMenuRadioGroup>) {
return <DropdownMenuRadioGroup data-slot="menubar-radio-group" {...props} />;
}
function MenubarRadioItem({
className,
...props
}: React.ComponentProps<typeof DropdownMenuRadioItem>) {
return (
<DropdownMenuRadioItem
data-slot="menubar-radio-item"
{...props}
className={(state) =>
cn(
"relative flex cursor-default items-center gap-2 rounded-base border-2 border-transparent py-1.5 pr-2 pl-8 text-sm font-base transition-colors select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function MenubarLabel({ className, ...props }: React.ComponentProps<typeof DropdownMenuLabel>) {
return (
<DropdownMenuLabel
data-slot="menubar-label"
{...props}
className={cn("px-2 py-1.5 text-sm font-heading data-inset:pl-8", className)}
/>
);
}
function MenubarSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuSeparator>) {
return (
<DropdownMenuSeparator
data-slot="menubar-separator"
{...props}
className={(state) =>
cn(
"-mx-1 my-1 h-0.5 bg-border",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function MenubarShortcut({
className,
...props
}: React.ComponentProps<typeof DropdownMenuShortcut>) {
return (
<DropdownMenuShortcut
data-slot="menubar-shortcut"
className={cn("ml-auto text-xs font-base tracking-widest", className)}
{...props}
/>
);
}
function MenubarSub(props: React.ComponentProps<typeof DropdownMenuSub>) {
return <DropdownMenuSub data-slot="menubar-sub" {...props} />;
}
function MenubarSubTrigger({
className,
...props
}: React.ComponentProps<typeof DropdownMenuSubTrigger>) {
return (
<DropdownMenuSubTrigger
data-slot="menubar-sub-trigger"
{...props}
className={(state) =>
cn(
"gap-2 rounded-base border-2 border-transparent px-2 py-1.5 text-sm font-base [&_svg:not([class*='size-'])]:size-4",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
function MenubarSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuSubContent>) {
return (
<DropdownMenuSubContent
data-slot="menubar-sub-content"
{...props}
className={(state) =>
cn(
"min-w-[8rem] rounded-base border-2 border-border bg-main p-1 font-base text-main-foreground duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
typeof className === "function" ? className(state) : className,
)
}
/>
);
}
export {
Menubar,
MenubarPortal,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarGroup,
MenubarSeparator,
MenubarLabel,
MenubarItem,
MenubarShortcut,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSub,
MenubarSubTrigger,
MenubarSubContent,
};
#Usage
"use client";
import { useState } from "react";
import {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
} from "@/components/ui/menubar";
export default function FileMenu() {
const [message, setMessage] = useState("No command selected.");
return (
<div className="grid gap-3">
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={() => setMessage("Created a local draft.")}>
New draft
</MenubarItem>
<MenubarItem disabled>Export</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
<p role="status">{message}</p>
</div>
);
}#API reference
| Prop / part | Purpose | Default |
|---|---|---|
Menubar.loopFocus | Wrap trigger keyboard navigation | true |
Menubar.modal | Limit interaction to an open menu | true |
Menubar.disabled | Disable all menus in the bar | false |
Menubar.orientation | Trigger navigation axis | horizontal |
MenubarContent.align / sideOffset | Popup placement | start / 8 |
MenubarCheckboxItem.checked / onCheckedChange | Checked command state | Uncontrolled |
MenubarContent.alignOffset | Popup alignment adjustment, in pixels | -4 |
#Accessibility
Keep labels short and commands meaningful. Arrow keys navigate triggers and menu items; Escape dismisses. MenubarShortcut only displays text: implement and test the corresponding keyboard handler separately. Make disabled commands visually identifiable, and do not use application-menu semantics for an ordinary list of website links.