Component
Menubar
A lilac tab track of medium triggers whose menus are the dropdown menu's raised panels.
npx -y github:anwarlemu/inkbox-brand-cli#release add menubarUsage
Use for an app-style menu of commands in a dense tool, such as an editor for an agent's notes. In the console's pages prefer buttons and row menus.
Avoid
- A menubar for site navigation (use Navigation Menu)
- More than six triggers
- Triggers without menus
Exports: Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger. This component is a client component.
Dependencies: surface, radix-ui, @phosphor-icons/react.
Example
"use client";import * as React from "react";import { CopyIcon, FileTextIcon, PlusIcon, TrashIcon } from "@phosphor-icons/react/ssr";import { Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger,} from "@/components/ui/menubar";function Bar({ open }: { open?: boolean }) { const [wrap, setWrap] = React.useState(true); const [format, setFormat] = React.useState("markdown"); return ( <Menubar defaultValue={open ? "note" : undefined}> <MenubarMenu value="note"> <MenubarTrigger>Note</MenubarTrigger> <MenubarContent className="w-56"> <MenubarGroup> <MenubarItem> <PlusIcon /> New note <MenubarShortcut>⌘N</MenubarShortcut> </MenubarItem> <MenubarItem> <CopyIcon weight="duotone" /> Duplicate <MenubarShortcut>⌘D</MenubarShortcut> </MenubarItem> <MenubarSub> <MenubarSubTrigger> <FileTextIcon weight="fill" /> Export as </MenubarSubTrigger> <MenubarSubContent className="w-40"> <MenubarItem>Markdown</MenubarItem> <MenubarItem>Plain text</MenubarItem> </MenubarSubContent> </MenubarSub> </MenubarGroup> <MenubarSeparator /> <MenubarItem variant="destructive"> <TrashIcon weight="fill" /> Delete note </MenubarItem> </MenubarContent> </MenubarMenu> <MenubarMenu value="edit"> <MenubarTrigger>Edit</MenubarTrigger> <MenubarContent className="w-52"> <MenubarItem> Undo <MenubarShortcut>⌘Z</MenubarShortcut> </MenubarItem> <MenubarItem disabled> Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut> </MenubarItem> </MenubarContent> </MenubarMenu> <MenubarMenu value="view"> <MenubarTrigger>View</MenubarTrigger> <MenubarContent className="w-52"> <MenubarCheckboxItem checked={wrap} onCheckedChange={(v) => setWrap(v === true)}> Wrap lines </MenubarCheckboxItem> <MenubarSeparator /> <MenubarLabel>Show as</MenubarLabel> <MenubarRadioGroup value={format} onValueChange={setFormat}> <MenubarRadioItem value="markdown">Markdown</MenubarRadioItem> <MenubarRadioItem value="formatted">Formatted</MenubarRadioItem> </MenubarRadioGroup> </MenubarContent> </MenubarMenu> </Menubar> );}export function Preview() { return <Bar />;}export function Open() { return ( <div className="min-h-80"> <Bar open /> </div> );}Source
"use client";import * as React from "react";import { Menubar as MenubarPrimitive } from "radix-ui";import { CaretRightIcon, CheckIcon } from "@phosphor-icons/react/ssr";import { usePortalContainer } from "@/lib/surface";import { cn } from "@/lib/utils";// A menu bar, extrapolated from the console (Figma 427:2715). The bar is the tab track: lilac with 10px corners and a// 2px inset, its triggers 36px with 8px corners and 14px medium ink, the open one a white key with Shadow/S. Its menus// are drawn as the dropdown menu (moving between them along the bar is instant: a closing panel that lingered would// dismiss the next one, and the move is made by keyboard and pointer many times): the panel is a raised white panel (8px corners, the floating// shadow) with 4px of padding; items are 36px rows with 8px corners like the rail's nav rows, a 20px glyph in the// action-glyph purple 10px from a 14px ink label, and the lilac under the highlighted row. Section labels are 13px in// the label purple ("Channels"), separators the rail's hairline, shortcuts 12px muted, destructive items red. The// panel grows out of its trigger from 96 percent with a fade; highlighting a row is instant (menus are used by keyboard// and many times a day).const CONTENT = "z-50 max-h-(--radix-menubar-content-available-height) min-w-48 overflow-x-hidden overflow-y-auto rounded-control bg-surface p-1 text-fg shadow-floating outline-none data-[state=open]:animate-pop-in";const ITEM = "relative flex h-9 cursor-default items-center gap-2.5 rounded-control px-2.5 font-sans text-body leading-tight text-fg outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-subtle data-[inset]:pl-10.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5.5 [&_svg:not([class*='text-'])]:text-icon-secondary";function Menubar({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Root>) { return <MenubarPrimitive.Root data-slot="menubar" className={cn("flex h-10 w-fit items-center gap-1 rounded-panel bg-subtle p-0.5", className)} {...props} />;}function MenubarMenu(props: React.ComponentProps<typeof MenubarPrimitive.Menu>) { return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />;}function MenubarPortal(props: React.ComponentProps<typeof MenubarPrimitive.Portal>) { const container = usePortalContainer(); return <MenubarPrimitive.Portal data-slot="menubar-portal" container={container} {...props} />;}function MenubarTrigger({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Trigger>) { return ( <MenubarPrimitive.Trigger data-slot="menubar-trigger" className={cn( "flex h-9 items-center gap-2 rounded-control px-2.5 font-sans text-body leading-tight font-medium text-fg outline-none select-none transition-[background-color,box-shadow] duration-150 ease-out hover:bg-subtle-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus-ring data-[state=open]:bg-surface data-[state=open]:shadow-s motion-reduce:transition-none", className, )} {...props} /> );}function MenubarContent({ className, align = "start", alignOffset = -2, sideOffset = 8, ...props }: React.ComponentProps<typeof MenubarPrimitive.Content>) { return ( <MenubarPortal> <MenubarPrimitive.Content data-slot="menubar-content" align={align} alignOffset={alignOffset} sideOffset={sideOffset} className={cn(CONTENT, "origin-(--radix-menubar-content-transform-origin)", className)} {...props} /> </MenubarPortal> );}function MenubarGroup(props: React.ComponentProps<typeof MenubarPrimitive.Group>) { return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />;}function MenubarItem({ className, inset, variant = "default", ...props }: React.ComponentProps<typeof MenubarPrimitive.Item> & { inset?: boolean; variant?: "default" | "destructive" }) { return ( <MenubarPrimitive.Item data-slot="menubar-item" data-inset={inset || undefined} data-variant={variant} className={cn(ITEM, "data-[variant=destructive]:text-danger data-[variant=destructive]:[&_svg]:text-danger", className)} {...props} /> );}function MenubarCheckboxItem({ className, children, checked, ...props }: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) { return ( <MenubarPrimitive.CheckboxItem data-slot="menubar-checkbox-item" checked={checked} className={cn(ITEM, "pl-10.5", className)} {...props}> <span className="pointer-events-none absolute left-2.5 flex size-5.5 items-center justify-center"> <MenubarPrimitive.ItemIndicator> <CheckIcon weight="bold" className="size-4.5 text-fg-brand" /> </MenubarPrimitive.ItemIndicator> </span> {children} </MenubarPrimitive.CheckboxItem> );}function MenubarRadioGroup(props: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) { return <MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />;}// The selected choice wears the console's radio mark: a pink ring around white.function MenubarRadioItem({ className, children, ...props }: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) { return ( <MenubarPrimitive.RadioItem data-slot="menubar-radio-item" className={cn(ITEM, "pl-10.5", className)} {...props}> <span className="pointer-events-none absolute left-2.5 flex size-5.5 items-center justify-center"> <MenubarPrimitive.ItemIndicator> <span className="block size-3.5 rounded-pill border-[3.5px] border-pink bg-surface" /> </MenubarPrimitive.ItemIndicator> </span> {children} </MenubarPrimitive.RadioItem> );}function MenubarLabel({ className, inset, ...props }: React.ComponentProps<typeof MenubarPrimitive.Label> & { inset?: boolean }) { return <MenubarPrimitive.Label data-slot="menubar-label" data-inset={inset || undefined} className={cn("px-2.5 pt-2 pb-1 font-sans text-body-sm leading-tight text-fg-label data-[inset]:pl-10.5", className)} {...props} />;}function MenubarSeparator({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Separator>) { return <MenubarPrimitive.Separator data-slot="menubar-separator" className={cn("-mx-1 my-1 h-px bg-line-subtle", className)} {...props} />;}function MenubarShortcut({ className, ...props }: React.ComponentProps<"span">) { return <span data-slot="menubar-shortcut" className={cn("ml-auto pl-4 font-sans text-caption text-fg-muted", className)} {...props} />;}function MenubarSub(props: React.ComponentProps<typeof MenubarPrimitive.Sub>) { return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;}function MenubarSubTrigger({ className, inset, children, ...props }: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & { inset?: boolean }) { return ( <MenubarPrimitive.SubTrigger data-slot="menubar-sub-trigger" data-inset={inset || undefined} className={cn(ITEM, "data-[state=open]:bg-subtle", className)} {...props}> {children} <CaretRightIcon className="ml-auto size-4.5 text-fg-muted" /> </MenubarPrimitive.SubTrigger> );}function MenubarSubContent({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.SubContent>) { return ( <MenubarPortal> <MenubarPrimitive.SubContent data-slot="menubar-sub-content" className={cn(CONTENT, "max-h-(--radix-menubar-content-available-height) origin-(--radix-menubar-content-transform-origin)", className)} {...props} /> </MenubarPortal> );}export { Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger,};Reference: Extrapolated from the Inkbox console (Figma 427:2715): the tab track (Mailboxes, Contact rules, Custom domains) and the raised panel.