GitHub

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 menubar
01

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

ComponentPropTypeDefaultDescription
MenubarclassNamestringAdditional CSS classes
MenubarItemonSelect() => voidCalled when item is selected (menu closes after)
disabledbooleanfalseDisable the item
insetbooleanfalseAdd left padding (for visual grouping)
classNamestringAdditional CSS classes
MenubarCheckboxItemcheckedbooleanfalseControlled checked state
defaultCheckedbooleanfalseUncontrolled initial checked state
onCheckedChange(checked: boolean) => voidCalled when checked state changes
disabledbooleanfalseDisable the item
MenubarRadioGroupvaluestringControlled value
defaultValuestring""Uncontrolled initial value
onValueChange(value: string) => voidCalled when value changes
MenubarRadioItemvaluestringUnique value for this radio item
disabledbooleanfalseDisable the item
MenubarSubdefaultOpenbooleanfalseStart with submenu open
openbooleanControlled open state
onOpenChange(open: boolean) => voidCalled when open state changes
React Principles