Components / structure

Menubar

Radix is permitted here — Menubar is on the closed list of six.

Rendered from the package · open on its own

Summary

A persistent horizontal bar of menus — the File / Edit / View strip.

APG patternMenubar
Built onRadix
Statusstable
Since0.1.0
<Menubar label="Editor" menus={…} onSelect={(item) => run(item.label)} />

Anatomy

A data-se="menubar" strip of triggers, each opening a floater that renders data-se="menu" so one set of rules dresses both.

PartElementRequired
floater<div>yes
item<radixmenubar.subtrigger>yes
label<span>yes
separator<radixmenubar.separator>yes
shortcut<span>no
trigger<radixmenubar.trigger>yes

Properties

PropertyValuesDefaultWhat it does
menusMenubarMenu[]requiredThe top-level menus, in display order. Each holds its own items.
labelstringnoneNames the menubar. It persists on the page, so it is a region a reader meets repeatedly.
onSelect(item: MenubarItem) => voidnoneCalled with the activated item. The bar closes the open menu first.

States

StateExpressed as
Focused:focus-visible
Hover:hover
Disabledaria-disabled
Highlighteddata-highlighted
Open or closeddata-state

Behavior

Left and Right move between triggers; Down, Up, Enter and Space open one. Up Arrow did nothing at all until the upstream port found it — Radix counts only Enter, Space and Down as keyboard opens, and the APG counts Up too.

Accessibility

One tab stop for the bar, with roving focus across the triggers.

Radix parks the tab stop on the menubar root rather than on an item, and gives the focused item tabindex="0" rather than -1. Both hold the requirement behind the APG's assertion, so the port asserts the invariant rather than the number.

Keyboard

KeyResult
Arrow Left / Arrow RightMove between triggers
Arrow Down / Arrow Up, Enter, SpaceOpen the focused menu
EscapeCloses the open menu
TabLeaves the bar entirely

Roles, states and properties

AttributeSet by
aria-labelset at render

Focus

One tab stop for the bar, with roving focus across the triggers. Radix parks the stop on the menubar root and gives the focused item tabindex="0" rather than -1; both hold the requirement behind the APG's assertion. Arrow Up did nothing at all until the upstream port found it.

Screen reader

Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.

Contrast and target size

npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.

It takes the touch floor under (pointer: coarse) — the visual size is unchanged on a desktop pointer.

Tested

  • test/apg/batch-e.test.jsx
  • test/browser/keyboard.spec.mjs

Known gaps

  • 10 upstream refs — APG's own page or markup, not the pattern
  • 19 upstream refs — a navigation menubar; the rest is their page chrome

Appearance

TokenUsed for
--se-accentcolor
--se-accent-softbackground
--se-bgbackground
--se-borderbackground, border, border-block-end
--se-border-widthborder, border-block-end
--se-control-hmin-block-size
--se-control-h-smblock-size
--se-duration-fastanimation
--se-ease-decelerateanimation
--se-focus-coloroutline
--se-focus-offsetoutline-offset
--se-focus-widthoutline, outline-offset
--se-font-sansfont-family
--se-icon-mdblock-size, inline-size
--se-icon-smblock-size, inline-size
--se-layer-floatingz-index
--se-leading-uiline-height
--se-mix-softbackground
--se-negativebackground, color
--se-radius-lgborder-radius
--se-radius-mdborder-radius
--se-radius-smborder-radius
--se-shadow-floatingbox-shadow
--se-space-1gap, margin, padding, padding-block
--se-space-2gap, inset-block-start, padding-inline
--se-space-3padding-inline
--se-surfacebackground
--se-surface-highlightbackground
--se-surface-sunkbackground
--se-target-minmin-block-size
--se-textcolor
--se-text-disabledcolor
--se-text-mutedcolor
--se-text-smfont-size

Motion

Floaters rise over --se-duration-fast, the same as Menu.

Content

Trigger labels are the conventional ones where conventions exist — File, Edit, View. A menubar is a place readers arrive with expectations, and inventing names spends them.

Usage

Reach for this last. It is desktop-application furniture: it implies a density of commands most pages do not have, and it spends a permanent strip of screen saying so. On the web, a Menu attached to a button is usually the honest answer.

It earns its place when the app really is an editor — when there are more commands than fit anywhere else, and readers will return often enough to learn where things live.

Notes

Radix is permitted here — Menubar is on the closed list of six.