Menubar
A desktop-style menu bar with top-level menus, nested dropdowns, checkbox and radio items, keyboard navigation, and shortcut hints.
Install
$
npx react-principles add menubar01
Features
- ✓Compound Components: Menu, Trigger, Content, Item, Separator, Sub, SubTrigger, CheckboxItem, RadioGroup, RadioItem, Shortcut
- ✓Nested Submenus: Support for arbitrarily nested dropdown menus
- ✓Checkbox Items: Toggle items with checked/unchecked state
- ✓Radio Items: Mutually exclusive selection within radio groups
- ✓Keyboard Navigation: Arrow keys, Escape, Enter/Space
- ✓Keyboard Shortcut Hints: Display shortcuts with Menubar.Shortcut
- ✓Auto-close: Escape or outside click closes the menu
02
Live Demo
03
Code Snippet
Menubar.tsx
import { Menubar } from "@/ui/Menubar"; function MyApp() { return ( <Menubar> <Menubar.Menu> <Menubar.Trigger>File</Menubar.Trigger> <Menubar.Content> <Menubar.Item onSelect={() => {}}> New Tab <Menubar.Shortcut>⌘T</Menubar.Shortcut> </Menubar.Item> <Menubar.Separator /> <Menubar.Item onSelect={() => {}}> Save <Menubar.Shortcut>⌘S</Menubar.Shortcut> </Menubar.Item> </Menubar.Content> </Menubar.Menu> </Menubar> ); }
04
Copy-Paste (Single File)
Menubar.tsx
"use client"; import React, { createContext, useCallback, useContext, useEffect, useRef, useState, type ButtonHTMLAttributes, type HTMLAttributes, type ReactElement, type ReactNode, } from "react"; import { cn } from "@/shared/utils/cn"; // ─── Types ──────────────────────────────────────────────────────────────────── export interface MenubarProps { children: ReactNode; className?: string; } export interface MenubarMenuProps { children: ReactNode; className?: string; } export interface MenubarTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> { children: ReactNode; } export interface MenubarContentProps extends HTMLAttributes<HTMLDivElement> { children: ReactNode; } export interface MenubarItemProps extends ButtonHTMLAttributes<HTMLButtonElement> { children: ReactNode; onSelect?: () => void; disabled?: boolean; inset?: boolean; } export interface MenubarCheckboxItemProps extends ButtonHTMLAttributes<HTMLButtonElement> { children: ReactNode; checked?: boolean; defaultChecked?: boolean; onCheckedChange?: (checked: boolean) => void; disabled?: boolean; } export interface MenubarRadioGroupProps { value?: string; defaultValue?: string; onValueChange?: (value: string) => void; children: ReactNode; className?: string; } export interface MenubarRadioItemProps extends ButtonHTMLAttributes<HTMLButtonElement> { value: string; children: ReactNode; disabled?: boolean; } export interface MenubarSeparatorProps extends HTMLAttributes<HTMLDivElement> {} export interface MenubarSubProps { children: ReactNode; className?: string; defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; } export interface MenubarSubTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> { children: ReactNode; } export interface MenubarShortcutProps extends HTMLAttributes<HTMLSpanElement> { children: ReactNode; } // See full source at /docs/menubar or run: npx react-principles add menubar
05
Usage Examples
Checkbox items
<Menubar.Menu> <Menubar.Trigger>View</Menubar.Trigger> <Menubar.Content> <Menubar.CheckboxItem defaultChecked> Show Toolbar </Menubar.CheckboxItem> <Menubar.CheckboxItem defaultChecked> Show Sidebar </Menubar.CheckboxItem> <Menubar.CheckboxItem> Show Status Bar </Menubar.CheckboxItem> </Menubar.Content> </Menubar.Menu>
Radio items
<Menubar.Menu> <Menubar.Trigger>Theme</Menubar.Trigger> <Menubar.Content> <Menubar.RadioGroup defaultValue="system"> <Menubar.RadioItem value="light">Light</Menubar.RadioItem> <Menubar.RadioItem value="dark">Dark</Menubar.RadioItem> <Menubar.RadioItem value="system">System</Menubar.RadioItem> </Menubar.RadioGroup> </Menubar.Content> </Menubar.Menu>
Nested submenu
<Menubar.Menu> <Menubar.Trigger>File</Menubar.Trigger> <Menubar.Content> <Menubar.Item onSelect={() => {}}>New</Menubar.Item> <Menubar.Sub> <Menubar.SubTrigger>Open Recent</Menubar.SubTrigger> <Menubar.Content> <Menubar.Item onSelect={() => {}}>home.html</Menubar.Item> <Menubar.Item onSelect={() => {}}>about.html</Menubar.Item> </Menubar.Content> </Menubar.Sub> <Menubar.Separator /> <Menubar.Item onSelect={() => {}}> Save <Menubar.Shortcut>⌘S</Menubar.Shortcut> </Menubar.Item> </Menubar.Content> </Menubar.Menu>
06
Props
| Component | Prop | Type | Default | Description |
|---|---|---|---|---|
| Menubar | className | string | — | Additional CSS classes |
| MenubarItem | onSelect | () => void | — | Called when item is selected (menu closes after) |
| disabled | boolean | false | Disable the item | |
| inset | boolean | false | Add left padding (for visual grouping) | |
| className | string | — | Additional CSS classes | |
| MenubarCheckboxItem | checked | boolean | false | Controlled checked state |
| defaultChecked | boolean | false | Uncontrolled initial checked state | |
| onCheckedChange | (checked: boolean) => void | — | Called when checked state changes | |
| disabled | boolean | false | Disable the item | |
| MenubarRadioGroup | value | string | — | Controlled value |
| defaultValue | string | "" | Uncontrolled initial value | |
| onValueChange | (value: string) => void | — | Called when value changes | |
| MenubarRadioItem | value | string | — | Unique value for this radio item |
| disabled | boolean | false | Disable the item | |
| MenubarSub | defaultOpen | boolean | false | Start with submenu open |
| open | boolean | — | Controlled open state | |
| onOpenChange | (open: boolean) => void | — | Called when open state changes |