Menubar
Menubar example: a desktop-style bar that holds several menus in one bordered strip. Reuses Dropdown Menu for content, items, shortcuts and nested submenus.
pnpm dlx shadcn add @01orbital/menubar"use client"
import * as React from "react"
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar"
import { CheckIcon } from "@phosphor-icons/react"
import { cn } from "cn"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
function Menubar({ className, ...props }: MenubarPrimitive.Props) {
return (
<MenubarPrimitive
data-slot="menubar"
className={cn("flex h-9 items-center rounded-3xl border p-1", 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 items-center rounded-2xl px-2 py-0.75 text-sm font-medium outline-hidden select-none hover:bg-muted aria-expanded:bg-muted",
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-48 rounded-3xl bg-popover p-1.5 text-popover-foreground shadow-lg ring-1 ring-foreground/5 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 dark:ring-foreground/10 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-2.5 rounded-2xl px-3 py-2 text-sm font-medium focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-9.5 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 data-disabled:opacity-50 [&_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-2.5 rounded-2xl py-2 pr-3 pl-9.5 text-sm font-medium outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-9.5 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-3 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-2.5 rounded-2xl py-2 pr-3 pl-9.5 text-sm font-medium outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-9.5 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-3 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-3.5 py-2.5 text-xs text-muted-foreground data-inset:pl-9.5",
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/50", 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-2 rounded-2xl px-3 py-2 text-sm font-medium focus:bg-accent focus:text-accent-foreground data-inset:pl-9.5 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-3xl bg-popover p-1.5 text-popover-foreground shadow-lg ring-1 ring-foreground/5 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 dark:ring-foreground/10 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,
}
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar"
// The copy: change text here, not in the markup.
const COPY = {
file: "File",
newTab: "New Tab",
newTabKeys: "⌘T",
newWindow: "New Window",
newWindowKeys: "⌘N",
newIncognito: "New Incognito Window",
share: "Share",
shareEmail: "Email link",
shareMessages: "Messages",
shareNotes: "Notes",
print: "Print...",
printKeys: "⌘P",
edit: "Edit",
undo: "Undo",
undoKeys: "⌘Z",
redo: "Redo",
redoKeys: "⇧⌘Z",
find: "Find",
findWeb: "Search the web",
findInPage: "Find...",
findNext: "Find Next",
findPrevious: "Find Previous",
cut: "Cut",
copy: "Copy",
paste: "Paste",
view: "View",
bookmarksBar: "Bookmarks Bar",
fullUrls: "Full URLs",
reload: "Reload",
reloadKeys: "⌘R",
forceReload: "Force Reload",
forceReloadKeys: "⇧⌘R",
fullscreen: "Toggle Fullscreen",
hideSidebar: "Hide Sidebar",
profiles: "Profiles",
editProfiles: "Edit...",
addProfile: "Add Profile...",
} as const
const PROFILES = [
{ value: "andy", name: "Andy" },
{ value: "benoit", name: "Benoit" },
{ value: "Luis", name: "Luis" },
] as const
export default function MenubarDemo() {
return (
<Menubar className="w-72">
<MenubarMenu>
<MenubarTrigger>{COPY.file}</MenubarTrigger>
<MenubarContent>
<MenubarGroup>
<MenubarItem>
{COPY.newTab} <MenubarShortcut>{COPY.newTabKeys}</MenubarShortcut>
</MenubarItem>
<MenubarItem>
{COPY.newWindow}{" "}
<MenubarShortcut>{COPY.newWindowKeys}</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>{COPY.newIncognito}</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarSub>
<MenubarSubTrigger>{COPY.share}</MenubarSubTrigger>
<MenubarSubContent>
<MenubarGroup>
<MenubarItem>{COPY.shareEmail}</MenubarItem>
<MenubarItem>{COPY.shareMessages}</MenubarItem>
<MenubarItem>{COPY.shareNotes}</MenubarItem>
</MenubarGroup>
</MenubarSubContent>
</MenubarSub>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem>
{COPY.print} <MenubarShortcut>{COPY.printKeys}</MenubarShortcut>
</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>{COPY.edit}</MenubarTrigger>
<MenubarContent>
<MenubarGroup>
<MenubarItem>
{COPY.undo} <MenubarShortcut>{COPY.undoKeys}</MenubarShortcut>
</MenubarItem>
<MenubarItem>
{COPY.redo} <MenubarShortcut>{COPY.redoKeys}</MenubarShortcut>
</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarSub>
<MenubarSubTrigger>{COPY.find}</MenubarSubTrigger>
<MenubarSubContent>
<MenubarGroup>
<MenubarItem>{COPY.findWeb}</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem>{COPY.findInPage}</MenubarItem>
<MenubarItem>{COPY.findNext}</MenubarItem>
<MenubarItem>{COPY.findPrevious}</MenubarItem>
</MenubarGroup>
</MenubarSubContent>
</MenubarSub>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem>{COPY.cut}</MenubarItem>
<MenubarItem>{COPY.copy}</MenubarItem>
<MenubarItem>{COPY.paste}</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>{COPY.view}</MenubarTrigger>
<MenubarContent className="w-44">
<MenubarGroup>
<MenubarCheckboxItem>{COPY.bookmarksBar}</MenubarCheckboxItem>
<MenubarCheckboxItem checked>{COPY.fullUrls}</MenubarCheckboxItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>
{COPY.reload} <MenubarShortcut>{COPY.reloadKeys}</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled inset>
{COPY.forceReload}{" "}
<MenubarShortcut>{COPY.forceReloadKeys}</MenubarShortcut>
</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>{COPY.fullscreen}</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>{COPY.hideSidebar}</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>{COPY.profiles}</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup value="benoit">
{PROFILES.map((profile) => (
<MenubarRadioItem key={profile.value} value={profile.value}>
{profile.name}
</MenubarRadioItem>
))}
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>{COPY.editProfiles}</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>{COPY.addProfile}</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}
Dependencies
npm: @base-ui/react, @phosphor-icons/react, cn. The CLI installs them for you.
Registry pieces it installs with it: