Skip to main content

Menubar

Horizontal menu bar with multiple menus (File, Edit, View) and full keyboard navigation.

import * as Menubar from '@gentleduck/primitives/menubar'
import * as Menubar from '@gentleduck/primitives/menubar'

Anatomy

<Menubar.Root>
  <Menubar.Menu>
    <Menubar.Trigger />
    <Menubar.Portal>
      <Menubar.Content>
        <Menubar.Item />
        <Menubar.Separator />
        <Menubar.Sub>
          <Menubar.SubTrigger />
          <Menubar.SubContent>
            <Menubar.Item />
          </Menubar.SubContent>
        </Menubar.Sub>
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Menu>
</Menubar.Root>
<Menubar.Root>
  <Menubar.Menu>
    <Menubar.Trigger />
    <Menubar.Portal>
      <Menubar.Content>
        <Menubar.Item />
        <Menubar.Separator />
        <Menubar.Sub>
          <Menubar.SubTrigger />
          <Menubar.SubContent>
            <Menubar.Item />
          </Menubar.SubContent>
        </Menubar.Sub>
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Menu>
</Menubar.Root>

Example

<Menubar.Root className="flex gap-1 bg-white border rounded-md p-1">
  <Menubar.Menu>
    <Menubar.Trigger className="px-3 py-1 rounded hover:bg-gray-100 text-sm">
      File
    </Menubar.Trigger>
    <Menubar.Portal>
      <Menubar.Content className="bg-white shadow-lg rounded-md p-1 min-w-[160px] border">
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">New File</Menubar.Item>
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">Save</Menubar.Item>
        <Menubar.Separator className="h-px bg-gray-200 my-1" />
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">Exit</Menubar.Item>
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Menu>
 
  <Menubar.Menu>
    <Menubar.Trigger className="px-3 py-1 rounded hover:bg-gray-100 text-sm">
      Edit
    </Menubar.Trigger>
    <Menubar.Portal>
      <Menubar.Content className="bg-white shadow-lg rounded-md p-1 min-w-[160px] border">
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">Undo</Menubar.Item>
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">Redo</Menubar.Item>
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Menu>
</Menubar.Root>
<Menubar.Root className="flex gap-1 bg-white border rounded-md p-1">
  <Menubar.Menu>
    <Menubar.Trigger className="px-3 py-1 rounded hover:bg-gray-100 text-sm">
      File
    </Menubar.Trigger>
    <Menubar.Portal>
      <Menubar.Content className="bg-white shadow-lg rounded-md p-1 min-w-[160px] border">
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">New File</Menubar.Item>
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">Save</Menubar.Item>
        <Menubar.Separator className="h-px bg-gray-200 my-1" />
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">Exit</Menubar.Item>
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Menu>
 
  <Menubar.Menu>
    <Menubar.Trigger className="px-3 py-1 rounded hover:bg-gray-100 text-sm">
      Edit
    </Menubar.Trigger>
    <Menubar.Portal>
      <Menubar.Content className="bg-white shadow-lg rounded-md p-1 min-w-[160px] border">
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">Undo</Menubar.Item>
        <Menubar.Item className="px-3 py-1.5 text-sm rounded hover:bg-gray-100">Redo</Menubar.Item>
      </Menubar.Content>
    </Menubar.Portal>
  </Menubar.Menu>
</Menubar.Root>

API

PropTypeDefaultDescription
valuestring-Controlled value of the currently open menu
defaultValuestring-Initial open menu (uncontrolled)
onValueChange(value: string) => void-Called when the active menu changes
loopbooleantrueKeyboard navigation wraps from last to first
dir'ltr' | 'rtl'-Reading direction for keyboard navigation

Groups a trigger and its content into one menu entry.

PropTypeDefaultDescription
valuestring-Unique value identifying this menu
onOpenChange(open: boolean) => void-Called when this menu opens or closes. Useful for integrating with animation libraries like motion
PropTypeDefaultDescription
align'start' | 'center' | 'end''start'Alignment relative to the trigger
sideOffsetnumber0Distance from the trigger
alignOffsetnumber0Cross-axis offset
forceMounttrue-Force content to stay mounted. Useful for animation libraries
asChildbooleanfalseRender as child element instead of wrapper
loopbooleanfalseWhether keyboard navigation loops around
trapFocusbooleancontext.openOverride whether focus is trapped inside the content
disableOutsidePointerEventsbooleancontext.openOverride whether outside pointer events are disabled
disableOutsideScrollboolean-Override whether scroll is locked when content is open
onCloseAutoFocus(event: Event) => void-Called when auto-focusing on close
onEscapeKeyDown(event: KeyboardEvent) => void-Called when Escape is pressed
onPointerDownOutside(event: PointerDownOutsideEvent) => void-Called when a pointer down event occurs outside
onFocusOutside(event: FocusOutsideEvent) => void-Called when focus moves outside the content
onInteractOutside(event: PointerDownOutsideEvent | FocusOutsideEvent) => void-Called on any outside interaction

Keyboard interactions

KeyAction
ArrowRightMove to next menu in the bar
ArrowLeftMove to previous menu in the bar
ArrowDownOpen menu / highlight next item
ArrowUpHighlight previous item
Enter / SpaceActivate item
EscapeClose menu