Navigation
Menubar.
A row of menus like a desktop app's menu bar, with shortcuts, checkbox and radio items and submenus.
"use client";
import * as React from "react";
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar";
export default function MenubarDemo() {
const [lineNumbers, setLineNumbers] = React.useState(true);
const [wordWrap, setWordWrap] = React.useState(false);
const [theme, setTheme] = React.useState("lantern");
const [turtle, setTurtle] = React.useState("turtle-07");
return (
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
New program <MenubarShortcut>Ctrl N</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Open... <MenubarShortcut>Ctrl O</MenubarShortcut>
</MenubarItem>
<MenubarSub>
<MenubarSubTrigger>Open recent</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>excavate.lua</MenubarItem>
<MenubarItem>startup.lua</MenubarItem>
<MenubarItem>guestbook.lua</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>
Save <MenubarShortcut>Ctrl S</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Save to disk... <MenubarShortcut>Shift Ctrl S</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem variant="destructive">Close editor</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Undo <MenubarShortcut>Ctrl Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>Ctrl Y</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Cut <MenubarShortcut>Ctrl X</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Copy <MenubarShortcut>Ctrl C</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Paste <MenubarShortcut>Ctrl V</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Find <MenubarShortcut>Ctrl F</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem checked={lineNumbers} onCheckedChange={setLineNumbers}>
Line numbers
</MenubarCheckboxItem>
<MenubarCheckboxItem checked={wordWrap} onCheckedChange={setWordWrap}>
Word wrap
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarLabel inset>Theme</MenubarLabel>
<MenubarRadioGroup value={theme} onValueChange={setTheme}>
<MenubarRadioItem value="lantern">Lantern</MenubarRadioItem>
<MenubarRadioItem value="amber">Amber monitor</MenubarRadioItem>
<MenubarRadioItem value="green">Green monitor</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Turtle</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Run program <MenubarShortcut>F5</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Stop <MenubarShortcut>Ctrl T</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarLabel inset>Upload to</MenubarLabel>
<MenubarRadioGroup value={turtle} onValueChange={setTurtle}>
<MenubarRadioItem value="turtle-07">turtle-07</MenubarRadioItem>
<MenubarRadioItem value="turtle-12">turtle-12</MenubarRadioItem>
<MenubarRadioItem value="miner-01">miner-01</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarItem disabled>Refuel (no coal)</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
);
}Installation#
With the CLI
$npx lanterncn add menubarThis also installs the Lantern theme and any Lantern UI components it depends on. The shadcn CLI works directly too: npx shadcn@latest add httptim/lantern-ui/menubar. See Installation if your project is not set up yet.
Manually
Install the dependencies
npm install radix-ui lucide-reactCopy the source into your project
components/ui/menubar.tsx"use client"; import * as React from "react"; import { Menubar as MenubarPrimitive } from "radix-ui"; import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; import { cn } from "@/lib/utils"; const menuPanel = "z-50 min-w-[10rem] overflow-x-hidden overflow-y-auto rounded-lg border bg-popover p-1 text-popover-foreground shadow-block-sm data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 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"; const menuItem = "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-[13px] outline-none select-none focus:bg-secondary focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground"; function Menubar({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Root>) { return ( <MenubarPrimitive.Root data-slot="menubar" className={cn("flex h-10 w-fit max-w-full items-center gap-0.5 overflow-x-auto rounded-md border bg-card p-1", 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>) { return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />; } function MenubarTrigger({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Trigger>) { return ( <MenubarPrimitive.Trigger data-slot="menubar-trigger" className={cn( "flex shrink-0 cursor-default items-center rounded-sm px-2.5 py-1 text-[13px] font-medium text-muted-foreground outline-hidden select-none", "hover:bg-secondary hover:text-foreground focus:bg-secondary focus:text-foreground data-[state=open]:bg-secondary data-[state=open]:text-primary", className, )} {...props} /> ); } function MenubarContent({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }: React.ComponentProps<typeof MenubarPrimitive.Content>) { return ( <MenubarPrimitive.Portal> <MenubarPrimitive.Content data-slot="menubar-content" align={align} alignOffset={alignOffset} sideOffset={sideOffset} className={cn( menuPanel, "min-w-[12rem] max-h-(--radix-menubar-content-available-height) origin-(--radix-menubar-content-transform-origin)", className, )} {...props} /> </MenubarPrimitive.Portal> ); } 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} data-variant={variant} className={cn( menuItem, "data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/12 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:[&_svg]:!text-destructive", className, )} {...props} /> ); } function MenubarCheckboxItem({ className, children, checked, ...props }: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) { return ( <MenubarPrimitive.CheckboxItem data-slot="menubar-checkbox-item" className={cn(menuItem, "pl-8", className)} checked={checked} {...props} > <span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center"> <MenubarPrimitive.ItemIndicator> <CheckIcon className="size-4 text-primary" /> </MenubarPrimitive.ItemIndicator> </span> {children} </MenubarPrimitive.CheckboxItem> ); } function MenubarRadioGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) { return <MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />; } function MenubarRadioItem({ className, children, ...props }: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) { return ( <MenubarPrimitive.RadioItem data-slot="menubar-radio-item" className={cn(menuItem, "pl-8", className)} {...props} > <span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center"> <MenubarPrimitive.ItemIndicator> <CircleIcon className="size-2 fill-primary text-primary" /> </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} className={cn( "px-2 pt-2 pb-1.5 font-mono text-[10px] font-semibold tracking-[0.2em] text-success uppercase data-[inset]:pl-8", 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-border", className)} {...props} /> ); } function MenubarShortcut({ className, ...props }: React.ComponentProps<"span">) { return ( <span data-slot="menubar-shortcut" className={cn("ml-auto pl-4 font-mono text-[10px] tracking-[0.12em] text-muted-foreground uppercase", 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} className={cn(menuItem, "data-[inset]:pl-8 data-[state=open]:bg-secondary", className)} {...props} > {children} <ChevronRightIcon className="ml-auto size-4" /> </MenubarPrimitive.SubTrigger> ); } function MenubarSubContent({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.SubContent>) { return ( <MenubarPrimitive.Portal> <MenubarPrimitive.SubContent data-slot="menubar-sub-content" className={cn(menuPanel, "origin-(--radix-menubar-content-transform-origin)", className)} {...props} /> </MenubarPrimitive.Portal> ); } export { Menubar, MenubarMenu, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, };Update the import paths to match your project
The source imports
cnfrom@/lib/utils.
Usage#
import {
Menubar,
MenubarMenu,
MenubarCheckboxItem,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarLabel,
MenubarPortal,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar";<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>New program <MenubarShortcut>Ctrl N</MenubarShortcut></MenubarItem>
<MenubarSeparator />
<MenubarItem>Save</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>Once a menu is open, Left and Right arrows move between menus and hovering another trigger switches to it. Item parts share the Dropdown Menu API.