Skip to main content

rc-menu

Menu popup for command surfaces with keyboard navigation and typed activation events, following the WAI-ARIA Menu pattern.

Package
@rcarls/rc-menu
Element
<rc-menu>
Native dependency
Consumer supplies menu item controls
State model
Host focus with an active descendant
Main events
rc-menu-activaterc-menu-close

Installation

npm install @rcarls/rc-menu
import '@rcarls/rc-menu/define';

Live demo

Interaction model

rc-menu is the focus target and owns the menu role. It exposes the active slotted item through aria-activedescendant instead of moving DOM focus between items. Arrow Down and Arrow Up move the virtual cursor and wrap at the menu boundaries; Home and End select the first or last item. Enter, Space, and pointer interactions activate menu items, while Escape requests that the containing popup close.

The public focusFirst(), focusLast(), focusItem(), and focusItemAt() methods follow the same model: each focuses the host and updates the virtual cursor. The read-only items getter returns the current navigable elements in document order.

Direct focusable children become menuitem entries. Use role="menuitemcheckbox" or role="menuitemradio" with aria-checked for checked choices; activation updates the checked state before the activation event is dispatched. A labeled role="group" can contain one level of related items, and <hr> or role="separator" elements divide sections without joining keyboard navigation.

<rc-menu label="View">
<button type="button" role="menuitemcheckbox" aria-checked="true" value="details">
Show details
</button>
<hr />
<button type="button" value="duplicate">
<span>Duplicate</span>
<kbd data-menu-shortcut>Ctrl+D</kbd>
</button>
</rc-menu>

Nested menu triggers supply their own visual affordance through rc-menu-button's indicator slot. rc-menu does not generate an indicator, leaving iconography and writing-direction behavior to the application or theme.

Events

Both events bubble across shadow boundaries and are not cancelable.

EventDetailWhen
rc-menu-activate{ item, value, text, checked? }Enter, Space, or a pointer click activates an enabled item
rc-menu-close{ reason: 'escape' }Escape requests that the containing popup close

The activation value comes from data-value or value, text is the item's trimmed text content, and checked reports the resulting checkbox or radio state when applicable.

Theming

The default demo mode shows the component without a package theme. Use the shared preview controls on this page to compare inherited, light, and dark color schemes or to apply the optional Material theme only inside the demo frames.

Slotted rows remain in light DOM. rc-menu injects their structural base styles once per containing document or shadow root under @layer rc-base, so consumer styles outside that layer can override them. Inherited --rc-menu-* custom properties customize the host and rows; the component does not expose CSS parts. Add trailing keyboard hints with [data-menu-shortcut].

API

Properties

PropertyMarkupTypeDefaultDescription
labellabelstring''Accessible label for this menu. Synced to `aria-label` on the host. Prefer `aria-labelledby` for a visible label associated via ID.
itemsJS property onlyreadonly Element[]Not specifiedAll navigable menu item elements in document order.

Methods

MethodDescription
focusFirst()Move the virtual cursor and DOM focus to the first navigable item.
focusLast()Move the virtual cursor and DOM focus to the last navigable item.
focusItem($item?: Element | null)Move the virtual cursor to a specific item.
focusItemAt(index: number)Move the virtual cursor to the item at `index`.

Events

EventDetail typeDescription
rc-menu-activateNo detail type documentedFired when a menu item is activated via keyboard (Enter/Space) or pointer click.
rc-menu-closeNo detail type documentedFired when Escape is pressed.

Slots

NameDescription
defaultMenu items, groups, and separators. Supported content model: - Focusable elements (`<button>`, `<a>`, `[tabindex]`) become navigable `role="menuitem"` entries. - `role="menuitemcheckbox"` / `role="menuitemradio"` elements get a leading indicator column via `::before`; `aria-checked` is initialized when missing and updated on activation. - `role="group"` divs group related items; label them with `aria-label` or `aria-labelledby`. Optionally add a visible `[data-group-label]` element as first child of the group for section headings. - `role="separator"` / `<hr>` produce horizontal dividers between sections. - Within any item, `[data-menu-shortcut]` elements are pushed to the inline-end edge for keyboard hints. - Nested menu triggers supply their own trailing indicator through `rc-menu-button`'s `indicator` slot.

CSS Custom Properties

PropertyDefaultDescription
--rc-menu-min-width10emMinimum width of the menu panel.
--rc-menu-padding-block0.25emBlock (top/bottom) padding inside the panel.
--rc-menu-backgroundCanvasBackground color; falls back through --rc-surface.
--rc-menu-border1px solid ButtonBorderBorder; falls back through --rc-border.
--rc-menu-radiusvar(--rc-control-radius)Menu panel border radius.
--rc-menu-shadow0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)Box shadow; falls back through --rc-shadow.
--rc-menu-colorCanvasTextText color; falls back through --rc-field-text.
--rc-menu-item-min-block-size0Minimum block size for menu item rows.
--rc-menu-item-padding-blockvar(--rc-item-padding-block)Menu item block-axis padding.
--rc-menu-item-padding-inlinevar(--rc-item-padding-inline)Menu item inline-axis padding.
--rc-menu-item-gapvar(--rc-item-gap)Gap between flex children inside each item.
--rc-menu-item-transitionNot specifiedCSS transition applied to each item row.
--rc-menu-hover-bgcolor-mix(in srgb, Highlight 8%, transparent)Pointer-hover background.
--rc-menu-hover-colorinheritPointer-hover text color.
--rc-menu-active-bgcolor-mix(in srgb, Highlight 8%, transparent)Virtual-cursor active background.
--rc-menu-active-colorinheritVirtual-cursor active text color.
--rc-menu-check-size1.25emWidth of the leading indicator column for checkbox/radio items.
--rc-menu-check-colorinheritColor of the check/radio symbol.
--rc-menu-shortcut-size0.8emFont size of `[data-menu-shortcut]` labels.
--rc-menu-shortcut-colorGrayTextText color of `[data-menu-shortcut]` labels.
--rc-menu-disabled-colorGrayTextDisabled menu item text color.
--rc-menu-disabled-opacityvar(--rc-disabled-opacity)Disabled menu item opacity.
--rc-menu-separator-border1px solid ButtonBorderSeparator border.
--rc-menu-separator-margin-blockvar(--rc-control-gap)Separator block-axis margin.
--rc-menu-separator-margin-inline0pxSeparator inline margin; set to match item padding for inset separators.
--rc-menu-group-label-padding-block0.1emBlock padding on `[data-group-label]` section headings.
--rc-menu-group-label-colorGrayTextText color of `[data-group-label]` section headings.

CSS Parts

No CSS parts are documented in the custom elements manifest.