Components

Menu

Headless menu primitives for command menus, selection menus, groups, separators, and nested submenus.

When to Use

Use Menu for a temporary list of commands or settings, such as Duplicate, Archive, or Show grid. Use Select or Listbox when the main job is choosing a form value, NavigationMenu for links that move around a website, and Menubar when several top-level application menus must sit in one horizontal row.

Features

  • Full keyboard navigation for menu items and submenus.
  • Supports controlled and uncontrolled open state.
  • Supports checkbox and radio menu items.
  • Supports grouped items, separators, and nested submenus.
  • Supports configurable closeOnSelect, looping, escape close, side, align, and offsets.
  • Stack-aware Escape dismissal when nested inside parent overlays.
  • Exposes state data attributes for styling without shipping styles.

Import

import { Menu } from "@flowstack-ui/atom";

Anatomy

<Menu.Root>
  <Menu.Content>
    <Menu.Group>
      <Menu.Item />
      <Menu.CheckboxItem />
      <Menu.RadioGroup>
        <Menu.RadioItem />
      </Menu.RadioGroup>
    </Menu.Group>
    <Menu.Separator />
    <Menu.Sub>
      <Menu.SubTrigger />
      <Menu.SubContent>
        <Menu.Item />
      </Menu.SubContent>
    </Menu.Sub>
  </Menu.Content>
</Menu.Root>

API Reference

Root

Provides open state, selection defaults, item registration, and modal behavior. It does not render a DOM element; trigger primitives such as DropdownMenu and ContextMenu control it, while standalone examples can open it directly.

PropTypeDefault
childrenReactNoderequired
openboolean-
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void-
modalbooleantrue
closeOnSelectbooleantrue
loopbooleantrue
closeOnEscapebooleantrue

Content

Portals and positions the focus-managed menu surface. It restores focus when closed and locks document scrolling while a modal menu is open.

PropTypeDefault
childrenReactNoderequired
side"top" | "right" | "bottom" | "left""bottom"
align"start" | "center" | "end""start"
sideOffsetnumber4
loopbooleanroot value
ariaLabelstring-
anchorPoint{ x: number; y: number }-
ARIA attributeValues
aria-orientation"vertical"
aria-labelValue from ariaLabel
aria-labelledbyTrigger ID when a trigger exists and ariaLabel is absent
Data attributeValues
[data-slot]"menu-content"
[data-state]"open" | "closed"
[data-side]Resolved Floating UI side
[data-align]Resolved Floating UI alignment
[data-positioned]Present after positioning completes

Item

Renders an actionable menu item.

PropTypeDefault
childrenReactNoderequired
valuestringrequired
textValuestringText child or value
onSelect() => void-
disabledbooleanfalse
closeOnSelectbooleanroot value
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
aria-disabledPresent when disabled
Data attributeValues
[data-slot]"menu-item"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-value]Item value

CheckboxItem

Renders a menuitemcheckbox.

PropTypeDefault
childrenReactNoderequired
checkedbooleanfalse
onCheckedChange(checked: boolean) => void-
valuestringrequired
textValuestringText child or value
disabledbooleanfalse
closeOnSelectbooleanfalse
ARIA attributeValues
aria-checkedCurrent checked state
aria-disabledPresent when disabled
Data attributeValues
[data-slot]"menu-checkbox-item"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-checked]Present when checked
[data-value]Item value

RadioGroup

Provides radio selection state for RadioItem.

PropTypeDefault
childrenReactNoderequired
valuestring-
onValueChange(value: string) => void-
Data attributeValues
[data-slot]"menu-radio-group"

Radio item values are scoped to their parent radio group for menu highlighting and keyboard movement, so separate groups can reuse values such as "default" inside the same menu.

RadioItem

Renders a menuitemradio.

PropTypeDefault
childrenReactNoderequired
valuestringrequired
textValuestringText child or value
disabledbooleanfalse
closeOnSelectbooleanfalse
ARIA attributeValues
aria-checkedWhether this value is selected by RadioGroup
aria-disabledPresent when disabled
Data attributeValues
[data-slot]"menu-radio-item"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-checked]Present when selected
[data-value]Public radio value

Group

Groups related menu items with role="group".

PropTypeDefault
childrenReactNoderequired
Data attributeValues
[data-slot]"menu-group"

Separator

Renders a horizontal separator between groups of related commands.

ARIA attributeValues
aria-orientation"horizontal"
Data attributeValues
[data-slot]"menu-separator"

Sub

Provides controlled or uncontrolled open state for one nested submenu. It does not render a DOM element.

PropTypeDefault
childrenReactNoderequired
openboolean-
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void-

SubTrigger

Renders the parent menuitem that opens, closes, and labels its SubContent.

PropTypeDefault
childrenReactNoderequired
valuestringrequired
textValuestringText child or value
disabledbooleanfalse
ARIA attributeValues
aria-haspopup"menu"
aria-expandedSubmenu open state
aria-disabledPresent when disabled
Data attributeValues
[data-slot]"menu-sub-trigger"
[data-state]"open" | "closed"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-value]Trigger value

SubContent

Portals and positions the nested menu beside SubTrigger, with its own item registry, highlight state, typeahead, and nested submenu support.

PropTypeDefault
childrenReactNoderequired
sideOffsetnumber4
loopbooleantrue
ariaLabelstring-
ARIA attributeValues
aria-orientation"vertical"
aria-labelValue from ariaLabel
aria-labelledbySubTrigger ID when ariaLabel is absent
Data attributeValues
[data-slot]"menu-sub-content"
[data-menu-sub-content]Present on nested menu surfaces
[data-state]"open" | "closed"
[data-side]Resolved side, mirrored in RTL
[data-positioned]Present after positioning completes

Advanced compound components can use useMenuContext, useMenuRadioGroupContext, and useMenuSubContext. Their matching providers and context value types are also public exports.

Examples

Selection Menu

import { useState } from "react";
import { Menu } from "@flowstack-ui/atom";

export function ViewMenu() {
  const [grid, setGrid] = useState(true);
  const [density, setDensity] = useState("comfortable");

  return (
    <Menu.Root defaultOpen>
      <Menu.Content ariaLabel="View settings">
        <Menu.CheckboxItem
          value="grid"
          checked={grid}
          onCheckedChange={setGrid}
        >
          Show grid
        </Menu.CheckboxItem>
        <Menu.RadioGroup value={density} onValueChange={setDensity}>
          <Menu.RadioItem value="comfortable">Comfortable</Menu.RadioItem>
          <Menu.RadioItem value="compact">Compact</Menu.RadioItem>
        </Menu.RadioGroup>
      </Menu.Content>
    </Menu.Root>
  );
}

Nested Action Menu

import { Menu } from "@flowstack-ui/atom";

export function ActionsMenu() {
  return (
    <Menu.Root defaultOpen>
      <Menu.Content ariaLabel="Actions">
        <Menu.Item value="duplicate" onSelect={() => console.log("Duplicate")}>
          Duplicate
        </Menu.Item>
        <Menu.Sub>
          <Menu.SubTrigger value="move">Move to</Menu.SubTrigger>
          <Menu.SubContent>
            <Menu.Item value="archive">Archive</Menu.Item>
          </Menu.SubContent>
        </Menu.Sub>
      </Menu.Content>
    </Menu.Root>
  );
}

Accessibility

Follows the WAI-ARIA menu pattern. Content renders role="menu", items render the correct menu item roles, disabled items expose disabled semantics, and keyboard focus is managed inside the open menu. Portalled Menu content and submenu content register with a parent modal focus scope when opened inside Dialog, Drawer, or another modal primitive. Printable-character typeahead matches enabled item text; a single-character search cycles forward from the current matching item, while multi-character buffers match exact prefixes.

KeyDescription
ArrowDown / ArrowUpMoves highlight between enabled items
Home / EndMoves highlight to first or last enabled item
Enter / SpaceSelects the highlighted item
EscapeCloses the topmost submenu first, then the root menu when enabled
ArrowRight / ArrowLeftOpens or closes submenus based on direction
Printable characterTypeahead search

Changelog

See CHANGELOG.md.