Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/menubar.jsonInstalls to @/components/ui/menubar.
Manual installation
Install the dependencies:
pnpm add @base-ui/react@^1.8.0 lucide-react@^1.48.0Add the components it builds on: dropdown-menu.
Copy the source into your project:
'use client'
import { Menu as MenuPrimitive } from '@base-ui/react/menu'
import { Menubar as MenubarPrimitive } from '@base-ui/react/menubar'
import { CheckIcon } from 'lucide-react'
import * as React from 'react'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
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"
className={cn('flex h-10 items-center gap-0.5 rounded-lg border p-[3px]', className)}
{...props}
/>
)
}
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"
className={cn(
'flex h-8 items-center rounded-sm px-2.5 text-sm font-medium outline-hidden select-none hover:bg-muted focus-visible:bg-muted focus-visible:ring-3 focus-visible:ring-ring aria-expanded:bg-muted data-disabled:pointer-events-none data-disabled:opacity-50',
className,
)}
{...props}
/>
)
}
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}
className={cn(
'min-w-36 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 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',
className,
)}
{...props}
/>
)
}
function MenubarItem({
className,
inset,
variant = 'default',
...props
}: React.ComponentProps<typeof DropdownMenuItem>) {
return (
<DropdownMenuItem
data-slot="menubar-item"
data-inset={inset}
data-variant={variant}
className={cn(
"group/menubar-item gap-1.5 rounded-md px-1.5 py-1 text-sm focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive!",
className,
)}
{...props}
/>
)
}
function MenubarCheckboxItem({
className,
children,
checked,
inset,
...props
}: MenuPrimitive.CheckboxItem.Props & {
inset?: boolean
}) {
return (
<MenuPrimitive.CheckboxItem
data-slot="menubar-checkbox-item"
data-inset={inset}
className={cn(
'relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4">
<MenuPrimitive.CheckboxItemIndicator>
<CheckIcon />
</MenuPrimitive.CheckboxItemIndicator>
</span>
{children}
</MenuPrimitive.CheckboxItem>
)
}
function MenubarRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuRadioGroup>) {
return <DropdownMenuRadioGroup data-slot="menubar-radio-group" {...props} />
}
function MenubarRadioItem({
className,
children,
inset,
...props
}: MenuPrimitive.RadioItem.Props & {
inset?: boolean
}) {
return (
<MenuPrimitive.RadioItem
data-slot="menubar-radio-item"
data-inset={inset}
className={cn(
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4">
<MenuPrimitive.RadioItemIndicator>
<CheckIcon />
</MenuPrimitive.RadioItemIndicator>
</span>
{children}
</MenuPrimitive.RadioItem>
)
}
function MenubarLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuLabel> & {
inset?: boolean
}) {
return (
<DropdownMenuLabel
data-slot="menubar-label"
data-inset={inset}
className={cn('px-1.5 py-1 text-sm font-medium data-inset:pl-7', className)}
{...props}
/>
)
}
function MenubarSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuSeparator>) {
return (
<DropdownMenuSeparator
data-slot="menubar-separator"
className={cn('-mx-1 my-1 h-px bg-border', className)}
{...props}
/>
)
}
function MenubarShortcut({ className, ...props }: React.ComponentProps<typeof DropdownMenuShortcut>) {
return (
<DropdownMenuShortcut
data-slot="menubar-shortcut"
className={cn(
'ml-auto text-xs tracking-widest text-muted-foreground group-focus/menubar-item:text-accent-foreground',
className,
)}
{...props}
/>
)
}
function MenubarSub({ ...props }: React.ComponentProps<typeof DropdownMenuSub>) {
return <DropdownMenuSub data-slot="menubar-sub" {...props} />
}
function MenubarSubTrigger({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuSubTrigger> & {
inset?: boolean
}) {
return (
<DropdownMenuSubTrigger
data-slot="menubar-sub-trigger"
data-inset={inset}
className={cn(
"gap-1.5 rounded-md px-1.5 py-1 text-sm focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function MenubarSubContent({ className, ...props }: React.ComponentProps<typeof DropdownMenuSubContent>) {
return (
<DropdownMenuSubContent
data-slot="menubar-sub-content"
className={cn(
'min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 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',
className,
)}
{...props}
/>
)
}
export {
Menubar,
MenubarPortal,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarGroup,
MenubarSeparator,
MenubarLabel,
MenubarItem,
MenubarShortcut,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSub,
MenubarSubTrigger,
MenubarSubContent,
}A visually persistent menu common in desktop applications that gives quick access to a consistent set of commands, for example in a case management workspace. Built on Base UI Menubar with submenus, checkbox and radio items, labels and keyboard shortcuts.
Features
- Multiple top-level menus
- Submenus
- Checkbox and radio items
- Labels, separators and inset items
- Shortcut hints
- Destructive item variant
- Full keyboard navigation
Usage
import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarTrigger,
} from '@/components/ui/menubar'
export function CaseMenubar() {
return (
<Menubar>
<MenubarMenu>
<MenubarTrigger>Case</MenubarTrigger>
<MenubarContent>
<MenubarItem>
New case <MenubarShortcut>Ctrl+N</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>Print</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}A MenubarLabel names its group, so place it inside a MenubarGroup or MenubarRadioGroup.
Checkbox and radio items keep the menu open by default; pass closeOnClick to close it on selection.
Examples
Default
A full menubar with submenus, checkbox and radio items.
With Icons
Items with icons and a destructive action.
Checkbox Items
Controlled checkbox items for column visibility.
Radio Items
Controlled radio items for sorting.
In Workspace
A borderless menubar in the title bar of a case management window.
Disabled
A menubar with a disabled trigger (Edit) and disabled items among normal ones.
Accessibility
- Implements the WAI-ARIA menubar pattern
- Arrow keys move between menus and items; Enter and Space activate
- Typeahead jumps to items by their text
- Escape closes the open menu and returns focus to its trigger
- MenubarShortcut is only a visual hint; bind the shortcut separately