Skip to the content

ComponentsMenu

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"

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; for one menu, a dropdown menu.

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'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.

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.

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.

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

Keyboard
KeyBehaviour
TabMoves 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).
EnterorSpaceOn 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.
HomeorEndMoves to the first or last trigger, or the first or last item in a menu.
A–ZIn a menu, moves to the next item whose text starts with the letters typed.
EscapeCloses the menu and returns focus to its trigger.

API

Menubar

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

Menubar props
PropTypeDefaultDescription
asChildboolean
defaultValuestringThe menu open at the start, when it controls itself.
dir"ltr" | "rtl"The reading direction. The provider's dir by default.
loopbooleanWhether 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.
valuestringThe open menu's value, when you control it. Pair it with onValueChange.

MenubarPortal

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

MenubarPortal props
PropTypeDefaultDescription
containerElement | DocumentFragment | nullSpecify a container element to portal the content into.
forceMounttrueUsed 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.

MenubarMenu props
PropTypeDefaultDescription
__scopeMenubarScope
valuestringThe value that names this menu, for a controlled menubar.

MenubarTrigger

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

MenubarTrigger props
PropTypeDefaultDescription
asChildbooleanRender 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.

MenubarContent props
PropTypeDefaultDescription
align"center" | "start" | "end"startAlignment along the trigger: start (default), center or end.
alignOffsetnumberDistance in pixels along that side. -4 by default, so items line up with the trigger's text.
arrowPaddingnumber
asChildboolean
avoidCollisionsboolean
collisionBoundaryBoundary | Boundary[]
collisionPaddingnumber | Partial<Record<"top" | "bottom" | "left" | "right", number>>
forceMounttrueUsed to force mounting when more control is needed. Useful when controlling animation with React animation libraries.
hideWhenDetachedboolean
loopbooleanWhether 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
sideOffsetnumberDistance 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.

MenubarGroup props
PropTypeDefaultDescription
asChildboolean

MenubarSeparator

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

MenubarSeparator props
PropTypeDefaultDescription
asChildboolean

MenubarLabel

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

MenubarLabel props
PropTypeDefaultDescription
asChildboolean
insetbooleanIndent the label to line up with items that have a check or dot.

MenubarItem

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

MenubarItem props
PropTypeDefaultDescription
asChildbooleanRender the child element instead, with this part's behaviour and classes merged onto it.
disabledbooleanSkips the item and ignores clicks.
insetbooleanIndent 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.
textValuestringThe text type-ahead matches, when the content is not plain text.
variant"default" | "destructive"defaultdefault, 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.

MenubarCheckboxItem props
PropTypeDefaultDescription
asChildboolean
checkedboolean | "indeterminate"Whether it is checked: true, false or "indeterminate". Pair it with onCheckedChange.
disabledbooleanSkips the item and ignores clicks.
onCheckedChange((checked: boolean) => void)Called with the new state when it is chosen.
onSelect((event: Event) => void)
textValuestring

MenubarRadioGroup

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

MenubarRadioGroup props
PropTypeDefaultDescription
valuerequiredstringThe chosen item's value. Pair it with onValueChange.
asChildboolean
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.

MenubarRadioItem props
PropTypeDefaultDescription
valuerequiredstringThe value this item stands for.
asChildboolean
disabledbooleanSkips the item and ignores clicks.
onSelect((event: Event) => void)
textValuestring

MenubarSub

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

MenubarSub props
PropTypeDefaultDescription
defaultOpenboolean
onOpenChange((open: boolean) => void)Called with true or false when it opens or closes.
openbooleanWhether the submenu is open, when you control it.

MenubarSubTrigger

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

MenubarSubTrigger props
PropTypeDefaultDescription
asChildboolean
disabledbooleanSkips the item and ignores clicks.
insetbooleanIndent the item to line up with items that have a check or dot.
textValuestring

MenubarSubContent

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

MenubarSubContent props
PropTypeDefaultDescription
align"start" | "end"Controls the direction the subcontent appears from its anchor menu item Default: start
alignOffsetnumber
arrowPaddingnumber
asChildboolean
avoidCollisionsboolean
collisionBoundaryBoundary | Boundary[]
collisionPaddingnumber | Partial<Record<"top" | "bottom" | "left" | "right", number>>
forceMounttrueUsed to force mounting when more control is needed. Useful when controlling animation with React animation libraries.
hideWhenDetachedboolean
loopbooleanWhether keyboard navigation should loop around
onEscapeKeyDown((event: KeyboardEvent) => void)
onFocusOutside((event: FocusOutsideEvent) => void)
onInteractOutside((event: FocusOutsideEvent | PointerDownOutsideEvent) => void)
onPointerDownOutside((event: PointerDownOutsideEvent) => void)
sideOffsetnumberDistance 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.

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for
--accentbackground
--accent-foregroundtext
--borderbackground
--cardbackground
--card-foregroundtext
--control-hovertext
--destructive-strongtext
--destructive-subtlebackground
--muted-foregroundtext
--popoverbackground
--popover-foregroundtext