# Menubar

A bar of menus, such as File, Edit and View, for an editor or a tool with many commands.

- **Import:** `import { Menubar, MenubarPortal, MenubarMenu, MenubarTrigger, MenubarContent, MenubarGroup, MenubarSeparator, MenubarLabel, MenubarItem, MenubarShortcut, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarSub, MenubarSubTrigger, MenubarSubContent } from "@booleanpress/ui/menubar"`
- **Radix Menubar:** <https://www.radix-ui.com/primitives/docs/components/menubar>
- **APG Menubar:** <https://www.w3.org/WAI/ARIA/apg/patterns/menubar/>
- **Page:** <https://ui.booleanpress.com/components/menubar> · @booleanpress/ui 0.2.0

## Usage

A menubar suits an application screen with many commands, such as an email template editor. It is a single tab stop: the arrow keys move between its menus. For links between pages, use a [navigation menu](/components/navigation-menu); for one menu, a [dropdown menu](/components/dropdown-menu).

```tsx
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarTrigger } from "@booleanpress/ui/menubar"

export function EditorMenus() {
  return (
    <Menubar>
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem onSelect={() => save()}>Save</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}
```

Each `MenubarMenu` holds a `MenubarTrigger` and its `MenubarContent`. Once a menu is open, moving the pointer or the arrow keys to another trigger opens that menu instead. The items are the [dropdown menu](/components/dropdown-menu)'s: `MenubarCheckboxItem`, `MenubarRadioItem` inside `MenubarRadioGroup`, `MenubarSub` for a nested list, `MenubarLabel`, `MenubarSeparator`, `MenubarGroup` and `MenubarShortcut`, which shows a key hint without binding the key. A trigger takes an icon before its text. The open menu is controlled with `value` and `onValueChange` on `Menubar`, each `MenubarMenu` naming itself with `value`.

## Examples

### Basic

File, Edit and View menus, with shortcuts, submenus and a disabled item.

```tsx
import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarSeparator,
  MenubarShortcut,
  MenubarSub,
  MenubarSubContent,
  MenubarSubTrigger,
  MenubarTrigger,
} from "@booleanpress/ui/menubar"

export default function MenubarBasic() {
  return (
    <Menubar>
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>New template<MenubarShortcut>⌘N</MenubarShortcut></MenubarItem>
          <MenubarItem>Open<MenubarShortcut>⌘O</MenubarShortcut></MenubarItem>
          <MenubarSeparator />
          <MenubarSub>
            <MenubarSubTrigger>Export as</MenubarSubTrigger>
            <MenubarSubContent>
              <MenubarItem>HTML</MenubarItem>
              <MenubarItem>Plain text</MenubarItem>
            </MenubarSubContent>
          </MenubarSub>
          <MenubarSeparator />
          <MenubarItem>Send a test email<MenubarShortcut>⌘T</MenubarShortcut></MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>Edit</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Undo<MenubarShortcut>⌘Z</MenubarShortcut></MenubarItem>
          <MenubarItem>Redo<MenubarShortcut>⇧⌘Z</MenubarShortcut></MenubarItem>
          <MenubarSeparator />
          <MenubarSub>
            <MenubarSubTrigger>Insert</MenubarSubTrigger>
            <MenubarSubContent>
              <MenubarItem>Customer name</MenubarItem>
              <MenubarItem>Order number</MenubarItem>
              <MenubarItem>Unsubscribe link</MenubarItem>
            </MenubarSubContent>
          </MenubarSub>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>View</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Desktop preview</MenubarItem>
          <MenubarItem>Mobile preview</MenubarItem>
          <MenubarSeparator />
          <MenubarItem disabled>Dark mode preview</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}
```

### Checkbox and radio items

Show or hide panels with checkbox items, and choose one width with radio items.

```tsx
import { useState } from "react"
import {
  Menubar,
  MenubarCheckboxItem,
  MenubarContent,
  MenubarLabel,
  MenubarMenu,
  MenubarRadioGroup,
  MenubarRadioItem,
  MenubarSeparator,
  MenubarTrigger,
} from "@booleanpress/ui/menubar"

export default function MenubarCheckboxRadio() {
  const [panels, setPanels] = useState({ outline: true, variables: false })
  const [width, setWidth] = useState("600")

  return (
    <Menubar>
      <MenubarMenu>
        <MenubarTrigger>View</MenubarTrigger>
        <MenubarContent>
          <MenubarLabel>Panels</MenubarLabel>
          <MenubarCheckboxItem checked={panels.outline} onCheckedChange={(outline) => setPanels({ ...panels, outline })}>
            Outline
          </MenubarCheckboxItem>
          <MenubarCheckboxItem checked={panels.variables} onCheckedChange={(variables) => setPanels({ ...panels, variables })}>
            Variables
          </MenubarCheckboxItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>Layout</MenubarTrigger>
        <MenubarContent>
          <MenubarLabel>Email width</MenubarLabel>
          <MenubarRadioGroup value={width} onValueChange={setWidth}>
            <MenubarRadioItem value="480">480 px</MenubarRadioItem>
            <MenubarRadioItem value="600">600 px</MenubarRadioItem>
            <MenubarRadioItem value="720">720 px</MenubarRadioItem>
          </MenubarRadioGroup>
          <MenubarSeparator />
          <MenubarCheckboxItem checked disabled>Centre the content</MenubarCheckboxItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}
```

### With icons

An icon before each trigger's text and each item's.

```tsx
import { CopyIcon, EyeIcon, FileIcon, FolderOpenIcon, PencilIcon, RedoIcon, SaveIcon, UndoIcon } from "lucide-react"
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@booleanpress/ui/menubar"

export default function MenubarWithIcons() {
  return (
    <Menubar>
      <MenubarMenu>
        <MenubarTrigger><FileIcon />File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem><FolderOpenIcon />Open</MenubarItem>
          <MenubarItem><SaveIcon />Save</MenubarItem>
          <MenubarItem><CopyIcon />Duplicate</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger><PencilIcon />Edit</MenubarTrigger>
        <MenubarContent>
          <MenubarItem><UndoIcon />Undo</MenubarItem>
          <MenubarItem><RedoIcon />Redo</MenubarItem>
          <MenubarSeparator />
          <MenubarItem><CopyIcon />Copy</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger><EyeIcon />View</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Desktop preview</MenubarItem>
          <MenubarItem>Mobile preview</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}
```

## Accessibility

**Semantics.** The bar is `role="menubar"`; each trigger is a `menuitem` with `aria-haspopup="menu"` and `aria-expanded`. Each open list is `role="menu"`, with `menuitem`, `menuitemcheckbox` and `menuitemradio` items; a submenu trigger has `aria-haspopup="menu"`.

**Labels.** Triggers and items are named by their text. Give the bar an `aria-label` when the page has more than one menubar.

**Focus.** The bar is one tab stop, on the last trigger used. Opening a menu moves focus into it; closing returns focus to its trigger. Focus shows as the `--accent` fill.

**Known limits.**

- Only text and icons in items; a menu is not a place for form controls.
- On narrow screens the bar does not collapse into one button. Keep it short, or use a dropdown menu there.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Tab | Moves into the bar, onto one trigger, and out of it again. |
| → | Moves to the next trigger, wrapping; in an open menu, opens the next menu (← in right-to-left pages). |
| ← | Moves to the previous trigger, wrapping; in an open menu, opens the previous menu (→ in right-to-left pages). |
| Enter or Space | On a trigger, opens its menu. On an item, chooses it and closes the menu. |
| ↓ | On a trigger, opens its menu on the first item. In a menu, moves to the next item. |
| ↑ | In a menu, moves to the previous item. |
| Home or End | Moves to the first or last trigger, or the first or last item in a menu. |
| A–Z | In a menu, moves to the next item whose text starts with the letters typed. |
| Escape | Closes the menu and returns focus to its trigger. |

## API

### Menubar

Renders Radix Menubar.Root and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `defaultValue` | `string` |  | The menu open at the start, when it controls itself. |
| `dir` | `"ltr" \| "rtl"` |  | The reading direction. The provider's `dir` by default. |
| `loop` | `boolean` |  | Whether the arrow keys wrap from the last trigger to the first. `true` by default. |
| `onValueChange` | `((value: string) => void)` |  | Called with the open menu's value, or an empty string when all close. |
| `value` | `string` |  | The open menu's value, when you control it. Pair it with `onValueChange`. |

### MenubarPortal

Renders Radix Menubar.Portal and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `container` | `Element \| DocumentFragment \| null` |  | Specify a container element to portal the content into. |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |

### MenubarMenu

Renders Radix Menubar.Menu and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `__scopeMenubar` | `Scope` |  |  |
| `value` | `string` |  | The value that names this menu, for a controlled menubar. |

### MenubarTrigger

Renders Radix Menubar.Trigger and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  | Render the child element instead, with this part's behaviour and classes merged onto it. |

### MenubarContent

Renders Radix Menubar.Content and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"center" \| "start" \| "end"` | `start` | Alignment along the trigger: `start` (default), `center` or `end`. |
| `alignOffset` | `number` |  | Distance in pixels along that side. -4 by default, so items line up with the trigger's text. |
| `arrowPadding` | `number` |  |  |
| `asChild` | `boolean` |  |  |
| `avoidCollisions` | `boolean` |  |  |
| `collisionBoundary` | `Boundary \| Boundary[]` |  |  |
| `collisionPadding` | `number \| Partial<Record<"top" \| "bottom" \| "left" \| "right", number>>` |  |  |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
| `hideWhenDetached` | `boolean` |  |  |
| `loop` | `boolean` |  | Whether keyboard navigation should loop around |
| `onCloseAutoFocus` | `((event: Event) => void)` |  | Event handler called when auto-focusing on close. Can be prevented. |
| `onEscapeKeyDown` | `((event: KeyboardEvent) => void)` |  | Called when Escape is pressed. Call `event.preventDefault()` to keep it open. |
| `onFocusOutside` | `((event: FocusOutsideEvent) => void)` |  |  |
| `onInteractOutside` | `((event: FocusOutsideEvent \| PointerDownOutsideEvent) => void)` |  |  |
| `onPointerDownOutside` | `((event: PointerDownOutsideEvent) => void)` |  |  |
| `side` | `"top" \| "bottom" \| "left" \| "right"` | `bottom` |  |
| `sideOffset` | `number` |  | Distance in pixels below the trigger. 8 by default. |
| `sticky` | `"always" \| "partial"` |  |  |
| `updatePositionStrategy` | `"always" \| "optimized"` |  |  |

### MenubarGroup

Renders Radix Menubar.Group and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### MenubarSeparator

Renders Radix Menubar.Separator and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### MenubarLabel

Renders Radix Menubar.Label and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `inset` | `boolean` |  | Indent the label to line up with items that have a check or dot. |

### MenubarItem

Renders Radix Menubar.Item and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  | Render the child element instead, with this part's behaviour and classes merged onto it. |
| `disabled` | `boolean` |  | Skips the item and ignores clicks. |
| `inset` | `boolean` |  | Indent the item to line up with items that have a check or dot. |
| `onSelect` | `((event: Event) => void)` |  | Called when the item is chosen. Call `event.preventDefault()` to keep the menu open. |
| `textValue` | `string` |  | The text type-ahead matches, when the content is not plain text. |
| `variant` | `"default" \| "destructive"` | `default` | `default`, or `destructive` for an action that removes something. |

### MenubarShortcut

Renders a `span` and passes it every other prop.

### MenubarCheckboxItem

Renders Radix Menubar.CheckboxItem and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `checked` | `boolean \| "indeterminate"` |  | Whether it is checked: `true`, `false` or `"indeterminate"`. Pair it with `onCheckedChange`. |
| `disabled` | `boolean` |  | Skips the item and ignores clicks. |
| `onCheckedChange` | `((checked: boolean) => void)` |  | Called with the new state when it is chosen. |
| `onSelect` | `((event: Event) => void)` |  |  |
| `textValue` | `string` |  |  |

### MenubarRadioGroup

Renders Radix Menubar.RadioGroup and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `string` |  | The chosen item's value. Pair it with `onValueChange`. |
| `asChild` | `boolean` |  |  |
| `onValueChange` | `((value: string) => void)` |  | Called with the value of the item that was chosen. |

### MenubarRadioItem

Renders Radix Menubar.RadioItem and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `string` |  | The value this item stands for. |
| `asChild` | `boolean` |  |  |
| `disabled` | `boolean` |  | Skips the item and ignores clicks. |
| `onSelect` | `((event: Event) => void)` |  |  |
| `textValue` | `string` |  |  |

### MenubarSub

Renders Radix Menubar.Sub and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultOpen` | `boolean` |  |  |
| `onOpenChange` | `((open: boolean) => void)` |  | Called with `true` or `false` when it opens or closes. |
| `open` | `boolean` |  | Whether the submenu is open, when you control it. |

### MenubarSubTrigger

Renders Radix Menubar.SubTrigger and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `disabled` | `boolean` |  | Skips the item and ignores clicks. |
| `inset` | `boolean` |  | Indent the item to line up with items that have a check or dot. |
| `textValue` | `string` |  |  |

### MenubarSubContent

Renders Radix Menubar.SubContent and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"start" \| "end"` |  | Controls the direction the subcontent appears from its anchor menu item Default: start |
| `alignOffset` | `number` |  |  |
| `arrowPadding` | `number` |  |  |
| `asChild` | `boolean` |  |  |
| `avoidCollisions` | `boolean` |  |  |
| `collisionBoundary` | `Boundary \| Boundary[]` |  |  |
| `collisionPadding` | `number \| Partial<Record<"top" \| "bottom" \| "left" \| "right", number>>` |  |  |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
| `hideWhenDetached` | `boolean` |  |  |
| `loop` | `boolean` |  | Whether keyboard navigation should loop around |
| `onEscapeKeyDown` | `((event: KeyboardEvent) => void)` |  |  |
| `onFocusOutside` | `((event: FocusOutsideEvent) => void)` |  |  |
| `onInteractOutside` | `((event: FocusOutsideEvent \| PointerDownOutsideEvent) => void)` |  |  |
| `onPointerDownOutside` | `((event: PointerDownOutsideEvent) => void)` |  |  |
| `sideOffset` | `number` |  | Distance in pixels from its trigger. |
| `sticky` | `"always" \| "partial"` |  |  |
| `updatePositionStrategy` | `"always" \| "optimized"` |  |  |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="menubar"` (Menubar), `data-slot="menubar-portal"` (MenubarPortal), `data-slot="menubar-menu"` (MenubarMenu), `data-slot="menubar-trigger"` (MenubarTrigger), `data-slot="menubar-content"` (MenubarContent), `data-slot="menubar-group"` (MenubarGroup), `data-slot="menubar-separator"` (MenubarSeparator), `data-slot="menubar-label"` (MenubarLabel), `data-slot="menubar-item"` (MenubarItem), `data-slot="menubar-shortcut"` (MenubarShortcut), `data-slot="menubar-checkbox-item"` (MenubarCheckboxItem), `data-slot="menubar-radio-group"` (MenubarRadioGroup), `data-slot="menubar-radio-item"` (MenubarRadioItem), `data-slot="menubar-sub"` (MenubarSub), `data-slot="menubar-sub-trigger"` (MenubarSubTrigger), `data-slot="menubar-sub-content"` (MenubarSubContent), and `data-inset`, `data-variant`.

## Theming

The bar is `--card` with a `--border` edge. A hovered, focused or open trigger, and the highlighted item, use `--accent`. Menus are `--popover`; icons, checks and dots use `--control-hover`, and `--muted-foreground` when highlighted; labels use `--muted-foreground`.

| Token | Used for |
| --- | --- |
| `--accent` | background |
| `--accent-foreground` | text |
| `--border` | background |
| `--card` | background |
| `--card-foreground` | text |
| `--control-hover` | text |
| `--destructive-strong` | text |
| `--destructive-subtle` | background |
| `--muted-foreground` | text |
| `--popover` | background |
| `--popover-foreground` | text |
