Components

Menubar

Headless horizontal menubar primitives for application-style menu systems.

When to Use

Use Menubar for desktop-like application commands grouped under headings such as File, Edit, and View. Use Menu for one standalone command menu, NavigationMenu for website navigation, and NavList for a normal list of links. Menubar is usually unnecessary for a small set of buttons.

Features

  • Renders a role="menubar" root with roving top-level trigger focus.
  • Opens adjacent top-level menus with ArrowLeft and ArrowRight.
  • Mirrors horizontal top-level navigation in RTL through dir or Direction.Provider.
  • Provides menu items, checkbox items, radio items, groups, separators, and submenus through the Menubar namespace.
  • Supports controlled and uncontrolled active top-level menu state.
  • Supports per-menu closeOnSelect, looping, and Escape close behavior.
  • Exposes state and styling data attributes without shipping styles.

Import

import { Menubar } from "@flowstack-ui/atom";

Anatomy

<Menubar.Root>
  <Menubar.Menu value="file">
    <Menubar.Trigger />
    <Menubar.Content>
      <Menubar.Group>
        <Menubar.Item />
        <Menubar.CheckboxItem />
        <Menubar.RadioGroup>
          <Menubar.RadioItem />
        </Menubar.RadioGroup>
      </Menubar.Group>
      <Menubar.Separator />
      <Menubar.Sub>
        <Menubar.SubTrigger />
        <Menubar.SubContent>
          <Menubar.Item />
        </Menubar.SubContent>
      </Menubar.Sub>
    </Menubar.Content>
  </Menubar.Menu>
</Menubar.Root>

API Reference

Menubar top-level coordination is implemented by Menubar.Root, Menubar.Menu, Menubar.Trigger, and Menubar.Content. The item, group, separator, and submenu parts share the same behavior as Menu, but are exposed directly on the Menubar namespace so consumers can build a complete menubar without importing Menu.

All DOM-rendering parts also accept their underlying native DOM props except for props Atom owns, such as managed roles and children.

Root

Contains the top-level menus and manages the active top-level menu value. Renders a div with role="menubar" and the resolved dir attribute.

PropTypeDefault
childrenReactNoderequired
valuestring | null-
defaultValuestring-
onValueChange(value: string | null) => void-
loopbooleantrue
dir"ltr" | "rtl"Direction.Provider
classNamestring-
ARIA attributeValues
aria-orientation"horizontal"
Data attributeValues
[data-slot]"menubar" by default

Provides one top-level menu scope. Each Menu value must be unique within the same Root.

PropTypeDefault
childrenReactNoderequired
valuestringrequired
closeOnSelectbooleantrue
loopbooleantrue
closeOnEscapebooleantrue

closeOnSelect is the default selection behavior for normal menu items inside that top-level menu. Checkbox and radio items default to staying open unless their own closeOnSelect prop is set.

Trigger

Opens and closes one top-level menu. Trigger renders a native button with role="menuitem" so it is a valid child of the menubar root. It uses roving tabIndex so only the active top-level trigger is tabbable.

PropTypeDefault
childrenReactNoderequired
disabledbooleanfalse
classNamestring-
ARIA attributeValues
aria-haspopup"menu"
aria-expandedtrue | false
aria-controlsAssociated menu content id
Data attributeValues
[data-slot]"menubar-trigger" by default
[data-state]"open" | "closed"
[data-disabled]Present when disabled

Content

Renders the positioned top-level menu surface for a Menu as a portalled div with role="menu" and tabIndex={-1}.

PropTypeDefault
childrenReactNoderequired
side"top" | "right" | "bottom" | "left""bottom"
align"start" | "center" | "end""start"
sideOffsetnumber4
loopbooleanmenu value
ariaLabelstring-
classNamestring-
ARIA attributeValues
aria-orientation"vertical"
aria-labelFrom ariaLabel
aria-labelledbyTrigger id when ariaLabel is not provided
Data attributeValues
[data-slot]"menu-content" by default
[data-state]"open" | "closed"
[data-side]Resolved side
[data-align]Resolved align
[data-positioned]Present after positioning is ready

Item

Renders an actionable menu item with role="menuitem" and tabIndex={-1}.

PropTypeDefault
childrenReactNoderequired
valuestringrequired
textValuestringchildren text or value
onSelect() => void-
disabledbooleanfalse
closeOnSelectbooleanmenu value
asChildbooleanfalse
renderRenderProp-
classNamestring-
ARIA attributeValues
aria-disabledPresent when disabled
Data attributeValues
[data-slot]"menu-item" by default
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-value]Item value

CheckboxItem

Renders a menuitemcheckbox with tabIndex={-1}.

PropTypeDefault
childrenReactNoderequired
valuestringrequired
textValuestringchildren text or value
checkedbooleanfalse
onCheckedChange(checked: boolean) => void-
disabledbooleanfalse
closeOnSelectbooleanfalse
classNamestring-
ARIA attributeValues
aria-checkedtrue | false
aria-disabledPresent when disabled
Data attributeValues
[data-slot]"menu-checkbox-item" by default
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-checked]Present when checked
[data-value]Item value

RadioGroup

Provides radio selection state for RadioItem. Renders a div with role="group".

PropTypeDefault
childrenReactNoderequired
valuestring-
onValueChange(value: string) => void-
classNamestring-
Data attributeValues
[data-slot]"menu-radio-group" by default

Radio item values are scoped to their parent radio group for highlighting and keyboard movement, so separate groups can reuse values such as "default" inside the same menubar menu.

RadioItem

Renders a menuitemradio with tabIndex={-1}.

PropTypeDefault
childrenReactNoderequired
valuestringrequired
textValuestringchildren text or value
disabledbooleanfalse
closeOnSelectbooleanfalse
classNamestring-
ARIA attributeValues
aria-checkedtrue | false
aria-disabledPresent when disabled
Data attributeValues
[data-slot]"menu-radio-item" by default
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-checked]Present when checked
[data-value]Public radio value

Group

Groups related menu items. Renders a div with role="group".

PropTypeDefault
childrenReactNoderequired
classNamestring-
Data attributeValues
[data-slot]"menu-group" by default

Separator

Renders a decorative separator with role="separator".

PropTypeDefault
classNamestring-
ARIA attributeValues
aria-orientation"horizontal"
Data attributeValues
[data-slot]"menu-separator" by default

Sub

Provides nested submenu state.

PropTypeDefault
childrenReactNoderequired
openboolean-
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void-

SubTrigger

Renders the item that opens a nested submenu with role="menuitem" and tabIndex={-1}.

PropTypeDefault
childrenReactNoderequired
valuestringrequired
textValuestringchildren text or value
disabledbooleanfalse
classNamestring-
ARIA attributeValues
aria-haspopup"menu"
aria-expandedtrue | false
aria-disabledPresent when disabled
Data attributeValues
[data-slot]"menu-sub-trigger" by default
[data-state]"open" | "closed"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-value]Trigger value

SubContent

Renders the positioned nested submenu surface as a portalled div with role="menu" and tabIndex={-1}.

PropTypeDefault
childrenReactNoderequired
sideOffsetnumber4
loopbooleantrue
ariaLabelstring-
classNamestring-
ARIA attributeValues
aria-orientation"vertical"
aria-labelFrom ariaLabel
aria-labelledbySubTrigger id when ariaLabel is not provided
Data attributeValues
[data-menu-sub-content]Present
[data-slot]"menu-sub-content" by default
[data-state]"open" | "closed"
[data-side]Resolved side
[data-positioned]Present after positioning is ready

Advanced top-level parts can use useMenubarContext and useMenubarMenuContext; the matching providers and context value types are also public exports. Shared item parts use the Menu contexts documented above.

Examples

Application Menu

import { Menubar } from "@flowstack-ui/atom";

export function ApplicationMenubar() {
  return (
    <Menubar.Root aria-label="Application commands">
      <Menubar.Menu value="file">
        <Menubar.Trigger>File</Menubar.Trigger>
        <Menubar.Content ariaLabel="File">
          <Menubar.Item value="new" onSelect={() => console.log("New file")}>
            New
          </Menubar.Item>
          <Menubar.Sub>
            <Menubar.SubTrigger value="export">Export</Menubar.SubTrigger>
            <Menubar.SubContent ariaLabel="Export">
              <Menubar.Item value="pdf">PDF</Menubar.Item>
              <Menubar.Item value="csv">CSV</Menubar.Item>
            </Menubar.SubContent>
          </Menubar.Sub>
        </Menubar.Content>
      </Menubar.Menu>
      <Menubar.Menu value="edit">
        <Menubar.Trigger>Edit</Menubar.Trigger>
        <Menubar.Content ariaLabel="Edit">
          <Menubar.Item value="undo">Undo</Menubar.Item>
        </Menubar.Content>
      </Menubar.Menu>
    </Menubar.Root>
  );
}

Selection Menu

import { useState } from "react";
import { Menubar } from "@flowstack-ui/atom";

export function ViewMenubar() {
  const [statusBar, setStatusBar] = useState(true);
  const [density, setDensity] = useState("comfortable");

  return (
    <Menubar.Root aria-label="View commands">
      <Menubar.Menu value="view">
        <Menubar.Trigger>View</Menubar.Trigger>
        <Menubar.Content ariaLabel="View">
          <Menubar.CheckboxItem
            value="status-bar"
            checked={statusBar}
            onCheckedChange={setStatusBar}
          >
            Status bar
          </Menubar.CheckboxItem>
          <Menubar.RadioGroup value={density} onValueChange={setDensity}>
            <Menubar.RadioItem value="compact">Compact</Menubar.RadioItem>
            <Menubar.RadioItem value="comfortable">Comfortable</Menubar.RadioItem>
          </Menubar.RadioGroup>
        </Menubar.Content>
      </Menubar.Menu>
    </Menubar.Root>
  );
}

Accessibility

Follows the WAI-ARIA menubar pattern. Top-level triggers use roving focus and open menus with keyboard or pointer input. Top-level triggers expose role="menuitem" so the role="menubar" root has valid menuitem children.

Horizontal ArrowLeft and ArrowRight navigation and nested submenu placement mirror in RTL when dir="rtl" is set on Menubar.Root or inherited from Direction.Provider. When an open menubar moves between top-level triggers, focus remains owned by the active trigger so Enter, Space, and Escape target the active menu.

Pointer-opened menus do not pre-highlight an item; ArrowDown and ArrowUp seed the first and last item highlight for keyboard navigation. Portalled menu content registers with a parent modal focus scope when opened inside Dialog, Drawer, or another modal primitive. Menubar menu content inherits Menu typeahead behavior for printable-character searches.

KeyDescription
ArrowRight / ArrowLeftMoves between top-level menus, mirrored in RTL
ArrowDownOpens a top-level menu and highlights the first item
ArrowUpOpens a top-level menu and highlights the last item
Home / EndMoves to the first or last top-level trigger, or item inside open content
Enter / Space on triggerOpens or closes the top-level menu
Enter / Space on itemSelects the highlighted item
EscapeCloses the topmost submenu first, then the active top-level menu
Printable characterTypeahead search inside open menu content

Changelog

See CHANGELOG.md.