Menubar
A desktop-app-style top menu bar (File / Edit / View). Click or hover expands submenus (cascading popups), with arrow key support, Alt access keys and a focus trap.
Multi-group radio (independent checkmarks per group)
The value of a type: "group" item acts as a group id. Leaves inside the same group record their selection independently; when value is a JSON object string ({"group-id":"selected"}), groups don't interfere — both "Mode" and "Theme" can show a checkmark at once.
Multi-group radio
Action items (kind: "action")
Leaves with kind: "action" render as plain actions (menuitem): no checkmark, clicks do not write back value, and only oas-select is dispatched (detail.kind === "action"). Fits "Open / Save / About" style items.
Action items (kind: action)
Shortcuts (shortcut)
The shortcut field (e.g. "Ctrl+N"): renders a key hint on the right, and auto-binds a document-level keydown — pressing the combo selects that item (preventDefault blocks the browser default). Binding rules: modifier+key combos bind directly; single keys bind only for function keys (F1–F12) — any other single key (letters/digits/Delete etc.) is display-only and not bound.
Shortcuts
Basic usage
Basic usage
Disabled items and groups
Submenus support disabled, type: "divider" separators and type: "group" group titles.
Disabled items and groups
Controlled selection
The value attribute is controlled (it is in observedAttributes): an external setAttribute('value', ...) takes effect immediately and syncs the selected item (check/highlight) to the corresponding leaf item; internal clicks also write back to value (uncontrolled channel), and the host can listen to oas-select to take over.
Controlled selection (value attribute)
Checkbox items and keep-open (checkbox + close-on-select)
Leaves with kind: "checkbox" toggle a multi-select checked set (value is a JSON array); toggling a checkbox does not close the submenu (continuous toggling). close-on-select="false" keeps the submenu open even after radio/action selections. close-on-select is a boolean attribute: presence means true (including an empty value); only an explicit "false" disables it — when the attribute is absent the default is to close.
Checkbox + close-on-select
Controlled open (open)
The open attribute holds the value of the currently open top-level menu (open="" closes all). Controlled attribute: an external setAttribute('open', ...) opens/switches/closes immediately; internal changes write open back and dispatch oas-open-change (detail: { value, open }) for the host to take over.
Controlled open (open attribute)
Trigger mode and arrow-key wrap (trigger / loop)
trigger="click" (default): a top-level menu opens on click first; once one is open, hovering another top-level item switches to it (desktop convention). trigger="hover" opens directly on hover. loop="false" disables wrap-around at the edges.
Hover opens directly (trigger)
No wrap-around (loop)
Disabled bar (disabled)
disabled disables the whole bar: top-level/sub-item clicks, keyboard navigation, shortcut hotkeys and Alt access keys are all blocked.
Disabled bar (disabled)
Popup positioning and arrow (side / align / offset / show-arrow)
side controls the first-level popup side (default bottom), align its alignment (default start), offset the gap to the trigger (px); show-arrow draws a visual arrow pointing at the trigger.
Popup positioning + arrow (side / align / offset / show-arrow)
Vertical orientation (orientation)
orientation="vertical": the bar stacks vertically, first-level popups default to the right; ↑/↓ move between top-level items, → opens a submenu.
Vertical (orientation)
Mobile hamburger folding (breakpoint)
breakpoint="600": when the viewport is ≤ 600px wide the bar folds into a hamburger button + popup menu (narrow-width folding); widen the window to restore the full bar.
Mobile hamburger (breakpoint)
Icons (icon)
Both top-level items and sub-items accept an icon field (icon-set name); icons follow the text color.
Icons (icon)
Icon color (iconColor)
Both top-level items and sub-items accept an iconColor field (fixes the icon color, overriding the selected/disabled default); it defaults to currentColor following the text color. The declarative child channel uses the icon-color attribute.
Icon color (iconColor / icon-color)
Link navigation (href)
Leaves (both inside submenus and top-level leaves) with href render as real <a> links: right-click new window and middle-click open work natively, good for SEO; clicks still dispatch oas-select and write back value (the link's default navigation is not blocked). target="_blank" automatically adds rel="noopener".
Link navigation (real href links)
Declarative child channel
Besides the items JSON, you can write items declaratively with <oas-menubar-item> / <oas-menubar-group> / <oas-menubar-divider> (the items attribute wins when explicitly set; otherwise child elements are parsed and converge to the same render path). The default slot text is the label; attributes map to the MenubarItem fields: value / disabled / icon / kind / danger / href / target / rel / shortcut / access-key / indeterminate. Nesting child elements directly inside <oas-menubar-item> recursively becomes a submenu; <oas-menubar-group> uses its label attribute as the group title (value can serve as a radio group id) and flattens its children to the same level. Child additions/removals, attribute and text changes re-render automatically (MutationObserver).
Declarative child channel (group / divider / nesting / checkbox / indeterminate / shortcut / danger / href)
Dynamic add/remove (MutationObserver auto-refresh)
Horizontal overflow folding (···)
When the container is too narrow, top-level items that don't fit fold into a trailing "···" item; clicking it opens a popup with the folded items (selecting works). When the selected item is folded, "···" highlights. Only in horizontal mode; vertical and the mobile hamburger don't fold. A folded top-level item that has a submenu is treated as a plain selectable item inside the popup (cascading submenus are not expanded there).
Overflow folding (narrow container)
Indeterminate checkboxes
kind: "checkbox" items support indeterminate: true: they render aria-checked="mixed" with a horizontal dash inside the box (distinct from the ✓ of a fully checked item), for parent/child "partially selected" scenarios. The half-selected state is computed by the host and passed via the items JSON; after toggling, the host can update the items to clear the flag.
Indeterminate checkbox
Leading/trailing slots (start / end)
slot="start" (logo position) / slot="end" (avatar position) decorative slots: rendered inside the bar when they have content; keyboard navigation skips them automatically (not part of arrow keys or the focus trap).
start / end slots (logo / avatar)
Put a logo/graphic in `start` and an avatar in `end`; arrow keys move between menu items and never jump into the slots.
API
oas-menubar
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
align | First-level popup alignment: start (default) / center / end; with side top/bottom it aligns on the horizontal axis, with left/right on the vertical axis | string | — |
breakpoint | Mobile breakpoint (px, e.g. 600): when the viewport width is ≤ the breakpoint the bar collapses into a hamburger button + popup menu (narrow-width folding) | string | — |
close-on-select | Whether selecting a leaf closes the open submenu, default true (desktop menubar convention); close-on-select="false" keeps it open (multi-select scenario); kind:"checkbox" items never close on toggle | string | — |
disabled | Disable the whole bar: top-level/sub-item clicks, keyboard navigation, shortcut hotkeys and Alt access keys are all blocked; visually desaturated | boolean | — |
items | Top-level menu items JSON (with submenu children) | string | [] |
loop | Arrow-key wrap-around toggle, default true (loops at edges); explicit loop="false" stops at the edges | string | — |
offset | Gap between the first-level popup and its trigger (px, default 4) | string | — |
open | Value of the currently open top-level menu (open="" closes all). Controlled attribute (in observedAttributes): an external setAttribute('open', ...) opens/switches/closes immediately; internal click/hover/keyboard changes write it back and dispatch oas-open-change, letting the host take over | string | — |
orientation | Arrangement direction: horizontal (default) / vertical (bar stacks vertically, first-level popups default to the right, arrow keys move top-level items up/down) | — | — |
show-arrow | Show a visual arrow on the popup pointing at the trigger | — | — |
side | First-level popup side: bottom (default horizontal) / top / left / right (default vertical); cascading submenus are unaffected | string | — |
trigger | Top-level menu trigger: click (default, click to open first, then hover switches — desktop convention) / hover (hover opens directly) | — | — |
value | Selected value. As a plain string it is a single global selection (no-group scenarios, backward compatible); as a JSON object string (e.g. {"mode":"preview","theme":"dark"}) selections are recorded per group id — the value of a type:"group" item acts as the group id; as a JSON array string (e.g. ["grid","wrap"]) it is the checkbox checked-set (kind:"checkbox" items, multi-select) | string | — |
Events
| Event | Description |
|---|---|
oas-open-change | The open top-level menu changed, detail: { value, open } (value = currently open top-level menu value, open = whether anything is open). Fired both on controlled setAttribute('open') and internal click/hover/keyboard changes (not on the first frame) |
oas-select | An item was selected, detail: { value, kind?, checked? }. kind only appears for action items (kind: "action"); checkbox items carry checked (new checked state); radio items omit detail.kind |
Slots
| Name | Description |
|---|---|
end | Trailing decorative slot (e.g. avatar): <div slot="end"> renders when it has content; keyboard navigation skips it |
start | Leading decorative slot (e.g. logo): <div slot="start"> renders when it has content; keyboard navigation skips it |
oas-menubar-item
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
access-key | Alt access key (single character); defaults to the first ASCII letter of the label | — | — |
danger | Destructive item: danger color semantics (delete/logout operations) | — | — |
disabled | Disable this item | — | — |
href | Link URL: with href the item renders as a native <a> (real navigation + still fires oas-select) | — | — |
icon | Leading icon (@oas-ui/icons registry icon name) | — | — |
icon-color | Icon color: fixes the icon to this color (overrides the selected/disabled default); defaults to currentColor following the text color | — | — |
indeterminate | Checkbox indeterminate state: renders aria-checked="mixed" + a dash inside the box (only for kind="checkbox" items) | — | — |
kind | Leaf semantics: radio (default, selectable) / action (no checked state, does not write back value) / checkbox (multi-select, value is the checked-set array) | — | — |
rel | Link rel (with href) | — | — |
shortcut | Shortcut hint (e.g. "Ctrl+N"): rendered as a trailing kbd + a document-level keydown binding that triggers select on hit | — | — |
target | Link target (with href) | — | — |
value | Selection value (data-carrier field of the declarative child channel) | — | — |
Slots
| Name | Description |
|---|---|
| default | Menubar item label content (default slot text); direct child <oas-menubar-item>/<oas-menubar-group>/<oas-menubar-divider> elements recursively become the submenu children |
oas-menubar-group
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
label | Group title (small secondary text, not clickable) | — | — |
value | Radio group id (picking inside the group only updates that group's selected value) | — | — |
Slots
| Name | Description |
|---|---|
| default | Group items: child <oas-menubar-item>/<oas-menubar-group>/<oas-menubar-divider> elements flatten to the same level |
oas-menubar-divider
Slots
| Name | Description |
|---|---|
| default | Divider data carrier (no attributes; the host parses it as type: "divider") |
Event detail note: the
detailofoas-selectis a component-internal object (value/kind) — not a nativeEvent, so you can'tpreventDefault()on it or read a nativeevent.targetfrom it. To reach the native event, use the outer parameter of your listener (e.g. inaddEventListener('oas-select', (e) => ...),eis aCustomEventande.detailis the component data).
MenubarItem fields (inherits MenuItem):
| Field | Description | Type |
|---|---|---|
label | Menu text | string |
value | Selection value (declared in the items JSON; after render the host tag carries a data-value lowercased attribute for internal lookup — hosts shouldn't rely on it as public API) | string |
kind | Leaf semantics: radio (default, checkable, participates in value) / action (action item, no checkmark, doesn't write back value on click) | string |
shortcut | Shortcut hint (e.g. "Ctrl+N"); renders as a right-side kbd and auto-binds a document-level keydown (selects the item on match, preventDefault) | string |
accessKey | Alt access key (single character); defaults to the first ASCII letter of label | string |
disabled | Disabled | boolean |
children | Submenu items (nested recursively, cascading to the right) | MenubarItem[] |
Keyboard: at top level ←/→ switch, ↓/Enter opens the submenu, Esc closes; inside a submenu ↑/↓ move, → enters a cascading submenu, ← returns to the parent; Home/End jump. Pressing Alt alone focuses the menu bar, Alt + access key opens the matching top-level menu. While a submenu is open, Tab cycles among its items (focus trap); roving tabindex keeps only the current top-level item tab-reachable.