Menu

A floating list of actions anchored to a trigger.

Basic

The .menu wrapper holds a trigger and a .menu__popup surface of .menu__item rows. Give the popup an id + data-stisla-menu, point a data-stisla-menu-trigger="id" button at it, and the @stisla/vanilla layer positions it with Floating UI and wires arrow-key navigation, typeahead, and outside-click / ESC dismiss. Navigable rows are matched by role, the keyboard cursor is data-highlighted, and a checkable row flips data-state="checked". The demos below are live. Rows take a leading icon and an optional trailing .menu__shortcut. A .menu__separator splits groups, and .menu__item--danger marks a destructive action.

Keyboard

The trigger behaves like a menu button. Open it with Enter, Space, or ArrowDown; the first enabled item takes focus.

  • ArrowDown / ArrowUp: move focus through enabled items (wraps at the ends)
  • Home / End: focus the first / last enabled item
  • Enter / Space: activate the focused item
  • Escape: close the menu and return focus to the trigger
  • Tab: close the menu and move focus to the next element on the page

With icons

Drop an <i data-lucide> as the first child of an item. The icon pins to 1rem and inherits the row color on hover so it tracks the surface naturally.

Headers and dividers

Use .menu__group-label to label a section and .menu__separator to separate groups. Wrap rows in a role="group" with aria-labelledby pointing at the header so screen readers announce the grouping.

Active and disabled

Mark the user's currently-applied choice with aria-current="true" or data-state="active". Both paint the persistent selected fill. Disabled rows take aria-disabled="true" on anchors or the native disabled attribute on buttons.

Destructive items

Add .menu__item--danger for actions that delete data or sign the user out. The color flips to the danger token and hover paints a soft danger tint instead of the standard accent fill so the row never reads like a routine choice.

Checkbox items

Items with role="menuitemcheckbox" toggle between checked and unchecked on click. The framework flips data-state and aria-checked; the leading .menu__indicator slot paints the check glyph when checked. The menu stays open between toggles via data-stisla-menu-auto-close="outside".

Radio items

Items with role="menuitemradio" inside a role="group" behave like a radio group. Clicking one item checks it and unchecks every sibling in the same group. They use the same indicator slot as checkbox items.

Item shortcuts

Append a .menu__shortcut chip after the label and auto-margin pushes it to the trailing edge of the row. Pair with <kbd> for the keystroke glyphs. The chip color inherits in hover and active paint so it stays readable on the highlight surface.

Media rows

Borrow the .media row for notification or message menus that pair an avatar or icon with a title and supporting lines. Give each row role="menuitem" and the menu folds it into keyboard navigation. The menu matches items by their role, so hover and arrow-key highlight paint the same as a plain item and round to the same row corners.

Placement

Set data-stisla-menu-placement on the menu to override the default bottom-start. Floating UI flips the menu automatically when it would overflow the viewport, so the value is a preference.

Events

Four events fire on the .menu root. The opening and closing events are cancelable.

stisla:menu:opening fires before the menu mounts. Call preventDefault() to abort.

stisla:menu:opened fires once the menu is positioned and focus has moved to the first item.

stisla:menu:closing fires before the menu dismisses. Call preventDefault() to keep it open.

stisla:menu:closed fires once the menu is hidden and focus has returned to the trigger.

Customization

These variables retune the menu. Override on the root or any wrapper.

VariableUse
--menu-z-indexOverlay stacking order
--menu-min-widthPopup minimum width
--menu-gapGap between rows
--menu-padding-block / -padding-inlinePopup interior padding; the inline value also feeds the concentric item radius
--menu-bg / -color / -border-colorPopup fill, text, and rim
--menu-radius / -shadowPopup corner radius and elevation
--menu-item-gap / -item-min-height / -item-padding-block / -item-padding-inlineRow layout
--menu-item-bg-hover / -item-color-hoverHover and keyboard-highlight paint (accent)
--menu-item-bg-active / -item-color-activeSelected-row paint (highlight)
--menu-item-color-disabledDisabled row text
--menu-item-color-danger / -item-bg-danger-hoverDestructive row text and hover tint
--menu-item-icon-size / -item-icon-colorLeading icon size and color (tracks row text by default)
--menu-shortcut-font-size / -shortcut-colorTrailing shortcut chip type
--menu-group-label-*Section label padding, type, and color
--menu-separator-color / -separator-margin-blockDivider stroke and spacing
--menu-transition-durationOpen fade timing; zeroed under reduced-motion