Menu
A standalone menu list with selection state and keyboard navigation.
Basic usage
Basic usage
Default selection
Default selection (value echo)
Disabled items
Disabled items
Nested submenu
Menu items with children show an expand arrow (›); clicking or hovering expands the submenu, which is rendered indented; press ArrowRight to enter and ArrowLeft to go back.
Nested submenu
Horizontal navigation
With mode="horizontal" the items are laid out in a row as a top navigation bar; first-level submenus pop down while second-level and deeper submenus still pop to the right.
Horizontal navigation (top bar style)
Collapsed state
collapsed (vertical / inline; not applicable to horizontal) narrows the menu to show only icons; in vertical mode hovering or clicking an icon item pops its submenu out to the right (still a full menu), while inline mode does not fly out — submenus still expand in place (click-to-expand, no hover expansion).
Collapsed (icons only)
Groups
Menu items with type: "group" render as a section with a group title (small text, secondary color, not clickable); the group's children are laid out flat on the same level and may mix in submenus and dividers.
Groups
With icons
icon uses icon names from @oas-ui/icons (built-in icon set) and renders an inline SVG to the left of the text.
With icons
Icon color (iconColor)
The iconColor field fixes the icon color (overriding the selected/disabled default); any CSS color or token works, and it defaults to currentColor following the text color. The declarative child channel uses the icon-color attribute.
Icon color (iconColor / icon-color)
Divider
type: "divider" renders a thin divider line that is not clickable and not part of keyboard navigation.
Divider
Declarative child channel
Besides the items JSON, you can write items declaratively with <oas-menu-item> / <oas-menu-group> / <oas-menu-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 items fields: value / disabled / loading / icon / kind / danger / href / target / rel. Nesting child elements directly inside <oas-menu-item> recursively becomes a submenu; <oas-menu-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 / danger / href)
Dynamic add/remove (MutationObserver auto-refresh)
Leading media (slot="leading")
Menu items support a leading media slot: put a <template slot="leading"> (or a direct child with slot="leading") inside <oas-menu-item>; the content renders before the label and takes precedence over the icon attribute (when both are given the icon is not rendered); in the loading state the spinner replaces the leading media, and in the collapsed state the media converges like icons (kept visible with zeroed spacing — hiding it would leave a hoverable blank item). Suited to avatars, color chips, and any rich leading content (items JSON does not support it — declarative children only).
Leading avatars (avatar example)
Dark menu
theme="dark" applies dark tokens locally (dark background + light text) to the menu, independent of the global theme; when unset, it follows the global theme.
Dark menu
Selection event
Selection event
Multi-select (checkbox)
Leaf items with kind: "checkbox" render as square checkboxes (role="menuitemcheckbox", distinct from the radio ✓); the checked set is written to value as a JSON array, and after a click oas-select carries checked in its detail (the state after this click).
Multi-select (checkbox)
Destructive items
danger: true applies red semantics (--oas-color-danger) for destructive actions such as delete or sign out; the red background deepens on hover / keyboard highlight.
Destructive items (danger)
Link items (href)
href renders the item as an <a> (anchor semantics: middle-click / right-click in a new window, SEO friendly); target / rel are passed through as-is. Clicking still fires oas-select and writes the selected state. The example links use target="_blank" so they open in a new tab and you stay on this page.
Link items (href)
Long menu scrolling
max-height caps the visible height of the menu (a plain number is treated as px); overflowing items scroll inside the menu — handy for long lists.
Long menu scrolling (max-height)
Typeahead
With the menu focused, typing a character jumps to the item whose label matches (character buffer with a 500ms idle reset; prefix match first, falls back to substring). The example labels include English so you can type: press c to jump to Copy, then u (combined cu) to jump to Cut.
Typeahead
Inline sidebar navigation
With mode="inline" submenus expand in place (no flyout) — the mainstream sidebar navigation form; expanding / collapsing animates the height, and multi-level nesting is supported.
Inline expand
Controlled expansion
expanded (JSON array) is a controlled attribute: setting / updating it from outside specifies which submenus are open. Every expand / collapse fires oas-expand-change (detail: { expanded, value, isExpanded }); in the controlled pattern the host writes the state back to expanded.
Controlled expanded
Keep open on select
Flyout modes (vertical / horizontal) collapse expanded submenus after a leaf is selected by default (expansion is temporary); close-on-select="false" keeps them open — handy for picking several items in a row. mode="inline" side navigation keeps submenus open by default (users need to see their section); close-on-select="true" changes that to collapse. kind="checkbox" items never collapse on toggle. close-on-select is a boolean attribute: presence means true (including an empty value such as close-on-select=""); only an explicit "false" disables it — the mode-based defaults above apply only when the attribute is absent.
Keep open on select (close-on-select)
vertical + close-on-select="false": the submenu stays open after picking a leaf
inline + close-on-select="true": the parent collapses after picking a leaf
Accordion
accordion (with mode="inline") makes sibling submenus mutually exclusive: expanding one automatically collapses the other open siblings.
Accordion (inline + accordion)
Horizontal overflow
With mode="horizontal", when the container is too narrow the overflowing items are automatically folded into a trailing "···" submenu — the nav bar never wraps or truncates.
Horizontal overflow
Hover to Expand Submenus
open-on-hover gives parent items in vertical / inline modes hover expansion: hovering for ~150ms opens the submenu, moving away closes it after ~300ms (moving back within the grace period cancels the close); the click path is unchanged (items still toggle on click). The mode="horizontal" top nav and the collapsed flyout already open instantly on hover and are unaffected by this attribute.
open-on-hover (vertical, delayed hover expansion)
vertical: hover "Edit" for ~150ms to open; it closes ~300ms after the pointer leaves
inline: hover also expands (multiple branches may stay open); clicking still toggles
Whole-menu disabled
disabled disables the entire menu: click / hover / keyboard are all intercepted (href items also get their native navigation blocked), the host text switches to the disabled color (no opacity overlay) and is synced with aria-disabled; removing the attribute restores interaction.
Whole-menu disabled
Pure action menu (selectable=false)
selectable="false" turns off selection semantics for the whole menu: every leaf item renders as an action item (menuitem, no check mark, no aria-checked), clicking only emits oas-select and never writes back value — for pure command menus ("click and go"; equivalent to writing every leaf as kind: "action").
Pure action menu (no check marks, value not written back)
Nothing clicked yet
Keep submenus open on select (persistent)
persistent keeps flyout submenus open after a selection (the positive switch of the close-on-select family; when explicit it wins over close-on-select): for continuous-pick scenarios where the menu should stay where it is; value is still written back.
persistent: submenu stays open after selecting
Hover a parent item to open its submenu, then click a leaf
Searchable (searchable)
searchable renders a filter input at the top of the menu and filters visible items live (works in both floating and inline modes): when a nested child matches, its ancestors are kept and the matching path is auto-expanded; when nothing matches, an i18n empty message is shown. Escape clears the search, ArrowDown moves focus into the menu.
Searchable (floating + inline)
Floating: type "Win" to filter down to "Window layout"
Inline: type "Stats" to filter in place and expand the matching path
Shortcut hints (shortcut)
The menu item shortcut field (items JSON) or the <oas-menu-item shortcut="…"> attribute renders a kbd hint at the trailing edge, matching the menubar shortcut visual contract.
Shortcut hints (shortcut)
API
oas-menu
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
accordion | Accordion mutual exclusion (inline mode: only one sibling submenu open at a time) | boolean | — |
close-on-select | Whether expanded submenus collapse after a leaf item is selected. Defaults by mode: inline side navigation keeps them open, flyout modes collapse; checkbox items never collapse on toggle | string | — |
collapsed | Collapsed state (vertical / inline; not applicable to horizontal): icons only, submenus pop to the right | boolean | — |
disabled | Disables the whole menu: click/hover/keyboard intercepted, dimmed with aria-disabled | boolean | — |
expanded | Controlled expanded set (JSON array string; which submenus are open in inline mode); internally managed when uncontrolled | string | — |
items | Menu items JSON (supports disabled / loading, icon, children submenus) | MenuItem[] | null | [] |
max-height | Max height of a long menu; scrolls internally beyond it (number gets px appended) | string | — |
mode | Layout mode: vertical (default) menu / horizontal top bar / inline side navigation (submenus expand in place with a height transition) | — | — |
open-on-hover | Hover-opened submenus in vertical/inline modes (~150ms open / ~300ms close delay); clicks unchanged; horizontal and collapsed flyout unaffected | boolean | — |
persistent | Keep flyout submenus open after selection (wins over close-on-select) | string | — |
searchable | Renders a filter input at the top of the menu and filters visible items live (matching ancestors are kept and auto-expanded; Esc clears) | boolean | — |
selectable | With "false", pure action menu: no check marks, clicks never write back value (detail kind=action) | string | true |
theme | Local theme: dark uses dark tokens (independent of the global theme) | — | — |
value | Current selected value. Plain string means global single-select (no group, legacy-compatible); JSON object string (e.g. {"sort":"name","view":"list"}) scopes per group id — the value of a type:"group" item is the group id, picking inside a group only updates that group | string | — |
Events
| Event | Description |
|---|---|
oas-expand-change | Submenu expand state changed, detail: { expanded: string[], value, isExpanded } (fired both controlled and uncontrolled) |
oas-select | Select an item, detail: { value, kind? }. kind only appears for action items (kind: "action") as "action"; radio items omit detail.kind |
CSS Variables
| CSS Variable | Default |
|---|---|
--oas-menu-max-height | none |
oas-menu-item
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
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 | — | — |
kind | Leaf semantics: radio (default, selectable) / action (no checked state, does not write back value) / checkbox (multi-select, value is the checked-set array) | — | — |
loading | Loading state: renders a spinner and blocks clicks; restored by data updates | — | — |
rel | Link rel (with href) | — | — |
shortcut | Shortcut hint rendered as a trailing kbd (aligned with the menubar shortcut contract) | — | — |
target | Link target (with href) | — | — |
value | Selection value (data-carrier field of the declarative child channel) | — | — |
Slots
| Name | Description |
|---|---|
| default | Menu item label content (default slot text); direct child <oas-menu-item>/<oas-menu-group>/<oas-menu-divider> elements recursively become the submenu children |
template[slot="leading"] | Leading media template (or a direct child with slot="leading"): avatars/color chips/any rich leading content, rendered before the label and taking precedence over the icon attribute; replaced by the spinner in the loading state; declarative child channel only (not supported by the items JSON) |
oas-menu-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-menu-item>/<oas-menu-group>/<oas-menu-divider> elements flatten to the same level |
oas-menu-divider
Slots
| Name | Description |
|---|---|
| default | Divider data carrier (no attributes; the host parses it as type: "divider") |
MenuItem fields:
| Field | Description | Type |
|---|---|---|
label | Menu item text | string |
value | Selection value | string |
type | Item type: item (default) / group (group title) / divider | string |
kind | Leaf semantics: radio (default, checkable) / action (action item, no checkmark, doesn't write back value on click) | string |
icon | Icon name (a key of @oas-ui/icons built-in icon set) | string |
iconColor | Icon color (optional): fixes the icon to this color, overriding the selected/disabled default; defaults to currentColor following the text color | string |
disabled | Disables the item | boolean |
children | Submenu items array with the same shape as the parent (nested recursively) | MenuItem[] |
children is an optional submenu items array; items with children expand their submenu on click/hover, and the selected state only lands on leaf items. group children are laid out flat on the same level; group titles are not clickable and skipped in keyboard navigation; divider items are not clickable and skipped in keyboard navigation.
Keyboard navigation: arrow keys move (auto-skipping group titles and dividers), Enter selects (items with submenus enter via Enter/ArrowRight), Home / End jump, ArrowLeft returns to the parent; role="menu" + menuitemradio (submenu parents are menuitem), the selected item shows a check mark.