Noctis

Menubar

An application menu bar: a row of top-level menus — File, Edit, View — whose dropdowns are ordinary Menu content. Arrow keys move between the menus, and opening one while another is open switches to it.

Basic

A Menubar.Root holds one Menubar.Menu per top-level menu. Each Menubar.Menu pairs a Menubar.Trigger (the labelled button in the bar) with a Menu.Content holding the usual Menu rows — items, separators, shortcuts, checkboxes, and radio groups. Menubar ships no item parts of its own; it reuses Menu's, so a bar menu and a standalone dropdown look identical.

Triggers are bare words by platform convention (macOS, VS Code, Word) — no chevron — and rest at full label contrast, so the bar reads as first-class navigation chrome. Name the bar for assistive technology with aria-label (or aria-labelledby) on Menubar.Root.

In an app header

A menubar is app-shell chrome — it belongs in a title strip, not floating on the page. Compose Menubar.Root inside a header Surface that carries the chrome (border, fill); the bar itself stays transparent, with a brand mark on the start and a command hint or avatar on the end.

Noctis
CommandkAL

With icons and submenus

Give a Menubar.Trigger a leading icon for a brand / app menu, and nest a Menu.SubmenuRoot inside any menu for a submenu — exactly as in a standalone Menu. Triggers have no chevron by default; opt one in with chevron for a dropdown-button-style trigger. modal={false} leaves the page interactive while a menu is open. A disabled trigger or item is grayed and announced as unavailable, with the resting cursor — never a forbidden one.

Button triggers

For a dropdown-button toolbar, render a trigger as a Noctis Button with Base UI's render prop — render={<Button variant="outline" size="sm" />}. The menubar's own ghost paint steps aside for any trigger carrying data-button, so the Button styles cleanly while the roving-focus and switch-on-hover behaviour stay intact. Pair it with chevron so each reads as a dropdown button.

Sizes

Menubar.Root takes a size (sm | md | lg, default md) that re-points the trigger height, padding, and label size across the bar. Reach for lg in a spacious application header.

Controlled open menu

Base UI tracks the open menu per Menubar.Menu, so the controlled-open seam lives on the menu (open / onOpenChange), not on the bar. Drive it from host state to open a menu for a guided tour, a deep link, or analytics.

Overflow

A real menu bar lives in a constrained strip. When the menus outgrow the width, collapse the spillover into a trailing Menubar.Overflow — an icon-only trigger that holds the menus that don't fit (the VS Code model). Measure which menus fit and render the rest as submenus inside the overflow menu; the reflow is instant and reduced-motion-safe. Menubar.Overflow defaults to a glyph and a localized "More menus" label.

Vertical

Set orientation="vertical" to stack the triggers into a left-aligned rail; Up / Down arrow keys traverse the bar and the menus open to the inline-end. Give the rail a width so the full-bleed triggers have a column to lead their labels from.

Best practices

A menubar is app-shell chrome for the title-bar File / Edit / View case — not the primary action surface. For primary actions prefer a command menu (the ⌘K pattern, as Linear does); it stays searchable and keyboard-driven. Keep the bar tight and quiet, and cap each menu near ~10 items (Geist) — reach for submenus or a command menu beyond that.

Keyboard

KeyAction
/ Move the roving focus to the next / previous top-level trigger (mirrored under RTL).
Home / EndFirst / last top-level trigger.
Enter / Space / On a trigger: open its menu and highlight the first item.
On a trigger: open its menu and highlight the last item.
/ With a menu open, move to and open the adjacent top-level menu (mirrored under RTL).
/ Move the highlight within the open menu.
EscClose the open menu and return focus to its trigger.
CharactersTypeahead — jump to the menu row whose label matches what you type.

While one menu is open, hovering a sibling trigger switches to its menu. A disabled trigger is announced as disabled and cannot be activated; it is skipped in arrow traversal (Base UI renders it as a native-disabled control), following the ARIA menu pattern.

Anatomy

Compose a menu bar from Menubar for the bar and triggers, and Menu for each dropdown.

  • Menubar.Root — the role="menubar" bar; a roving-tabindex row of triggers. Label it with aria-label / aria-labelledby. Modal by default (page scroll locked while a menu is open, outside content inert); pass modal={false} for a lighter bar. Takes size (sm | md | lg), orientation (horizontal | vertical), loopFocus, and disabled (disables the whole bar).
  • Menubar.Menu — owns one top-level menu's open state; renders no element of its own. It is a Menu.Root, so it accepts every Base UI Menu.Root prop, including open / defaultOpen / onOpenChange for the controlled open menu.
  • Menubar.Trigger — the labelled button in the bar that opens its menu. Optional leading icon; a trailing chevron (default off).
  • Menubar.Overflow — an icon-only trailing trigger for the spillover menus; shares the trigger slot, defaults to a glyph and a localized "More menus" aria-label.
  • Menu.Content and the Menu rows — the dropdown is ordinary Menu content: Menu.Item, Menu.CheckboxItem, Menu.RadioGroup/RadioItem, Menu.Group with a GroupLabel, Menu.Separator, Menu.Shortcut, and submenus via Menu.SubmenuRoot. See the Menu page for those parts.

Menubar's own parts carry a data-slot (menubar, menubar-trigger) for host-side styling — pair it with the state attributes (data-orientation, data-size, data-has-submenu-open on the bar; data-popup-open, data-highlighted, data-disabled, data-overflow on a trigger). The dropdown parts carry the Menu slots.

Design tokens

Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. The minted tokens are the public override seam: set one on any ancestor and every menu bar in that region retunes — e.g. .toolbar { --noctis-menubar-trigger-padding-inline: 0.5rem; } tightens every trigger beneath it. See Customization for the full override ladder and Tokens for the whole graph.

Token

API reference

Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table; parts that only forward to Base UI list just the props they pass through.

Menubar.Root

Prop

Menubar.Menu

Prop

Menubar.Trigger

Prop

Menubar.Overflow

Prop

AttributeDescription
data-slotThe bar element (and, with `data-slot=noctis-menubar-trigger`, each top-level trigger).
data-orientationThe bar's layout axis (`horizontal`/`vertical`), mirroring the root's `orientation` prop.
data-sizeThe bar's size step (`sm`/`md`/`lg`), driving the trigger metrics re-point.
data-has-submenu-openPresent on the bar while any of its menus is open.
data-popup-openPresent on a top-level trigger whose menu is open.
data-highlightedPresent on the top-level trigger the pointer or keyboard is currently over.
data-disabledPresent on a disabled top-level trigger.
data-overflowPresent on the icon-only overflow trigger (`Menubar.Overflow`); squares it off for the lone glyph.