Bridge view
Menu
A standalone list of commands carrying `role="menu"` plus children with `role="menuitem"`, `role="menuitemcheckbox"`, or `role="menuitemradio"`. Distinct from `MenuButton` (combined trigger + menu pattern) by being the menu surface itself — composable as a context-menu (right-click triggered), permanently-embedded surface, or sub-menu of a parent menu. Implements the APG menu keyboard model: arrow keys navigate items, Home / End jump to boundaries, typeahead jumps to items by initial letter, ArrowRight opens submenus, ArrowLeft / Escape closes submenus, roving-tabindex with single tab-stop into the menu.
Also called Context menu Dropdown menu Action list
When to use
Use
For surfaces presenting a list of commands the user picks one of — right-click context menus, embedded action lists, command palettes, sub-menus of a parent menu. Use the vertical variant for typical menu shapes; horizontal variant only when composing a top-of-page menubar (note: `role="menubar"` is a related but distinct ARIA pattern; horizontal `role="menu"` covers the rare horizontal-menu-not-menubar case). Compose with `MenuButton` when the menu needs a button trigger; compose standalone for context menus and embedded action lists.
Avoid
For lists that commit a value rather than invoke an action — that is `role="listbox"` (Combobox or Select). For arbitrary interactive content panels — that is `Popover` (with `role="dialog"`). For navigation between independent pages — that is `SidebarNav`. For toolbars (groups of related interactive controls without the menu keyboard model) — that is `role="toolbar"`. For multi-line forms inside a popover — use Popover, not Menu; the menu pattern's keyboard model blocks Tab and works against form usability.
Versus related
- menu-button
`MenuButton` combines a trigger button with a menu — button activates the menu, menu hosts the commands. `Menu` is the menu surface alone, composable independently of any specific trigger (right-click context menus, embedded action lists, sub-menus of a parent menu). MenuButton internally renders a Menu; the standalone Menu canonicalises the list-of-commands pattern for the cases where the trigger is not a button.
- popover
`Popover` hosts arbitrary interactive content with `role="dialog"` — the user may tab through controls inside the popover. `Menu` hosts a list of commands with `role="menu"` and the APG menu keyboard model (arrow keys + typeahead + roving tabindex; Tab closes or exits the menu). The role-distinction drives the keyboard contract; choose Popover for content panels, Menu for command lists. The `aria-haspopup` value on the trigger captures the distinction ("dialog" for Popover, "menu" for Menu).
Menu is the canonical surface for a list of commands the user picks one from. The reference documents the `role="menu"` plus child-role hierarchy (`menuitem` / `menuitemcheckbox` / `menuitemradio`), the typeahead contract that lets users jump to items by typing initial letters, the ArrowRight / ArrowLeft submenu navigation, the roving-tabindex (one menuitem at a time carries the tab-stop), and the boundary with `MenuButton` (which combines the trigger with this menu surface) and `Popover` (which hosts arbitrary content with `role="dialog"`, not action commands with `role="menu"`).
Implementations
How specific libraries realise the canonical anatomy. Each entry records the deltas between the canon and the library's surface.
Menu / MenuButton / MenuItems / MenuItem / MenuSection / MenuHeading / MenuSeparator import { Menu, MenuButton, MenuItems, MenuItem, MenuSection, MenuHeading, MenuSeparator,} from '@headlessui/react';
export function ActionsMenu() { return ( <Menu> <MenuButton className="rounded border px-3 py-1.5 data-open:bg-gray-100"> Actions </MenuButton>
<MenuItems anchor="bottom start" className="w-52 rounded shadow-lg bg-white ring-1 ring-black/5 focus:outline-none" > <MenuSection> <MenuHeading className="px-3 pt-2 pb-1 text-xs font-semibold text-gray-500"> File </MenuHeading> <MenuItem> {({ focus }) => ( <button className={`flex w-full px-3 py-1.5 text-sm ${focus ? 'bg-blue-500 text-white' : 'text-gray-900'}`} > New file </button> )} </MenuItem> <MenuItem disabled> {({ focus, disabled }) => ( <button className={`flex w-full px-3 py-1.5 text-sm ${disabled ? 'opacity-40 cursor-not-allowed' : focus ? 'bg-blue-500 text-white' : 'text-gray-900'}`} > Import (unavailable) </button> )} </MenuItem> </MenuSection>
<MenuSeparator className="my-1 border-t border-gray-100" />
<MenuSection> <MenuItem> {({ focus }) => ( <button className={`flex w-full px-3 py-1.5 text-sm ${focus ? 'bg-blue-500 text-white' : 'text-gray-900'}`} > Delete </button> )} </MenuItem> </MenuSection> </MenuItems> </Menu> );}Divergence
| From | Type | → To | Rationale |
|---|---|---|---|
anatomy[root] | reshaped | Two-part split: Menu (Fragment wrapper, no DOM element) plus MenuItems (renders role="menu"). Menu always includes a MenuButton trigger — there is no API path to render MenuItems without Menu wrapping a MenuButton. | The canonical menu anatomy is the standalone surface (role="menu" alone, composable with any trigger including right-click or programmatic open). Headless UI's Menu is a combined dropdown pattern: the Menu root wires MenuButton open/close state to MenuItems visibility. There is no exported standalone MenuItems component that works without a MenuButton — the open state is entirely driven by MenuButton interaction. Context-menu (right-click triggered) and embedded-action-list use-cases cannot be composed from Headless UI Menu without either forking the component or wrapping a hidden MenuButton. Source: https://headlessui.com/react/menu (Menu props, 2026-05-31). |
anatomy[submenu-trigger] | omitted | — | Headless UI Menu ships no submenu-trigger variant or nested Menu composition API. The library is explicitly single-level: there is no documented pattern for ArrowRight opening a nested role="menu", no aria-haspopup="menu" management, and no aria-expanded wiring for nested menus. Consumers who need submenus must compose two separate Menu instances with bespoke state management — none of the canonical APG submenu keyboard model (ArrowRight opens, ArrowLeft closes) is wired automatically. Source: https://headlessui.com/react/menu (no submenu section present, 2026-05-31); source file confirms no SubmenuTrigger export: https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-react/src/components/menu/menu.tsx (2026-05-31). |
anatomy[submenu] | omitted | — | Follows directly from the omission of submenu-trigger. No nested role="menu" composition is supported. Headless UI does not ship a Sub or SubTrigger component for Menu (unlike Radix DropdownMenu.Sub). Source: https://headlessui.com/react/menu (2026-05-31). |
anatomy[menuitemcheckbox] | omitted | — | Headless UI MenuItem always renders role="menuitem". There is no MenuItemCheckbox or equivalent that renders role="menuitemcheckbox" with aria-checked. Consumers who need toggle-state menu items must render a plain MenuItem and manually wire role="menuitemcheckbox" plus aria-checked via the `as` prop or by rendering a raw element inside the MenuItem render-prop. The library provides no built-in component, no aria-checked management, and no close-on-select=false default for toggle items. Source: https://headlessui.com/react/menu (MenuItem props — no checked/role variant, 2026-05-31); source file role assignment at line 793 confirms role="menuitem" is hardcoded: https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-react/src/components/menu/menu.tsx (2026-05-31). |
anatomy[menuitemradio] | omitted | — | Same root cause as menuitemcheckbox omission. No MenuItemRadio or MenuRadioGroup component is exported. Role="menuitemradio" with aria-checked and group-scoped mutual-exclusion is entirely absent. Consumers must author from scratch. Source: https://headlessui.com/react/menu (2026-05-31). |
anatomy[group] | reshaped | MenuSection (renders div, no role="group") + MenuHeading (renders header element). MenuSection carries no ARIA role by default; MenuHeading carries no aria-labelledby wiring to a MenuSection. | The canonical group slot requires role="group" with aria-labelledby for SR scoping of menuitemradio groups and semantic grouping of related menuitems. Headless UI's MenuSection renders a plain div with no role="group" and no aria-labelledby. MenuHeading renders a <header> element that is visually a section label but has no programmatic association with MenuSection via aria-labelledby. For menuitemradio groups (which require role="group" for correct SR scoping) this is a gap; for purely visual grouping of menuitems it is acceptable. Consumers who need the group semantic must set role="group" and aria-labelledby manually via the `as` prop. Source: https://headlessui.com/react/menu (MenuSection, MenuHeading props, 2026-05-31). |
anatomy[separator] | reshaped | MenuSeparator renders a div (default `as="div"`) with no role="separator" applied by the library. Consumers must either pass `as` with a semantic element or add role="separator" themselves. | The canonical separator slot documents role="separator" for programmatic section-boundary signalling to AT. Headless UI's MenuSeparator is a layout convenience: it renders a plain div with no ARIA role wired automatically. Consumers who rely on MenuSeparator for accessible separation must add `role="separator"` manually. The component is not focusable (no tabIndex), so the roving-tabindex-skip requirement is satisfied; only the role is absent. Source: https://headlessui.com/react/menu (MenuSeparator props, 2026-05-31). |
anatomy[icon] | omitted | — | Headless UI ships no icon slot component. All icon rendering is delegated to the consumer's render-prop children inside MenuItem. The library is intentionally unstyled and provides no slot for a leading icon with aria-hidden wiring. Consumers render icons directly in the MenuItem render-prop and manage aria-hidden themselves. Source: https://headlessui.com/react/menu (MenuItem render prop API, 2026-05-31). |
anatomy[shortcut] | omitted | — | No shortcut slot component is exported. Keyboard shortcut text ("Ctrl+S") must be authored by consumers inside the MenuItem render-prop. There is no built-in mechanism to wire aria-keyshortcuts on the parent menuitem; consumers must set it manually. Source: https://headlessui.com/react/menu (2026-05-31). |
axes.variants[horizontal] | omitted | — | Headless UI Menu has no orientation prop and does not support aria-orientation="horizontal". MenuItems renders role="menu" with vertical arrow-key navigation hardcoded — ArrowDown/Up navigate items; there is no ArrowLeft/Right navigation mode for horizontal menu layout. Source: https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-react/src/components/menu/menu.tsx (no orientation parameter, 2026-05-31). |
axes.properties[size] | omitted | — | Headless UI is intentionally unstyled; it ships no size prop (sm/md). All sizing is the consumer's responsibility via className or CSS custom properties. Source: https://headlessui.com/react/menu (2026-05-31). |
axes.properties[wrapNavigation] | omitted | — | The canonical wrapNavigation boolean controls whether ArrowDown on the last item wraps to the first. Headless UI's arrow-key navigation wraps by default with no opt-out prop. Consumers cannot disable wrapping. Source: https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-react/src/components/menu/menu.tsx (keyboard handler, 2026-05-31). |
axes.properties[closeOnSelect] | omitted | — | The canonical closeOnSelect boolean controls whether activating a menuitem closes the menu. Headless UI always closes MenuItems on MenuItem activation; there is no opt-out. The MenuItem render prop exposes a close() function allowing manual close from a custom handler, but there is no closeOnSelect=false API to keep the menu open after item activation (relevant for toggle-state items). Source: https://headlessui.com/react/menu (MenuItem render prop — close function, 2026-05-31). |
axes.properties[typeahead] | reshaped | Typeahead is always enabled with no opt-out prop. A-Z key input when the menu is open focuses the first matching item by its text content. | The canonical typeahead property is a boolean that consumers can toggle off. Headless UI ships typeahead as an unconditional behaviour: there is no typeahead={false} prop. This is a feature reduction from the canonical model (consumers cannot disable typeahead) but a correct implementation of the APG first-character search contract. Source: https://headlessui.com/react/menu (keyboard interactions — A-Z, 2026-05-31). |
anatomy[root] | extended | + `anchor` prop on MenuItems provides built-in floating positioning via Floating UI. Accepts a string ("bottom", "bottom start", "top end", etc.) or an object `{ to, gap, offset, padding }`. When anchor is set, portal is auto-enabled and MenuItems renders in a portal. CSS custom properties `--button-width`, `--anchor-gap`, `--anchor-offset`, and `--anchor-padding` are injected for consumer sizing. The `modal` prop (default true) enables scroll locking and inerting of other page elements when the menu is open. | The canonical anatomy does not prescribe a floating positioning API; that is left to implementors. Headless UI ships first-party positioning to remove the need for a third-party positioning library. The modal=true default adds scroll locking and background inerting which the canonical menu pattern (a standalone list surface) does not require — context menus and embedded action lists do not need scroll locking. Source: https://headlessui.com/react/menu (MenuItems props — anchor, portal, modal, 2026-05-31). |
anatomy[root] | extended | + `transition` boolean prop on MenuItems enables CSS transition support via data attributes (data-closed, data-enter, data-leave). When true, Headless UI manages enter/leave transition state so consumers can write CSS transitions without a separate Transition wrapper component. Also compatible with Framer Motion via AnimatePresence. | The canonical menu anatomy documents motion via a durations/easing token map but prescribes no specific transition API. Headless UI's transition prop is a library-specific convenience that maps the open/close lifecycle to CSS class hooks. No canonical equivalent exists; this is a purely additive API surface. Source: https://headlessui.com/react/menu (MenuItems props — transition, 2026-05-31). |
anatomy[root] | extended | + `static` boolean prop (default false) on MenuItems bypasses the library's internal open/close state — the panel is always rendered regardless of open state. `unmount` boolean prop (default true) controls whether MenuItems is removed from the DOM when closed (true) or kept in DOM as display:none (false). Both props are for advanced consumers who manage visibility externally or need to pre-render for performance. | The canonical anatomy has no equivalent lifecycle-override props. static and unmount address the trade-off between mount/unmount cost and pre-render cost: large menus (many items) may benefit from unmount=false to avoid re-mount on every open. The canonical performance section recommends pre-mounting with aria-hidden for fast open; Headless UI exposes the control explicitly. Source: https://headlessui.com/react/menu (MenuItems props — static, unmount, 2026-05-31). |
Why this audit reads the way it does
Headless UI React Menu (@headlessui/react, currently v2.x) is a combined dropdown-menu primitive — it always pairs a MenuButton trigger with a MenuItems panel. This is a structural departure from the canonical standalone Menu surface that can be composed with any trigger. The divergences cluster into four groups: 1. Combined-pattern architecture — Menu always requires MenuButton; there is no standalone role="menu" surface usable for context menus or embedded action lists without a Button trigger. This aligns with what the canonical vocabularyDrift entry for Headless UI notes explicitly. 2. Missing role variants — No menuitemcheckbox, menuitemradio, or submenu hierarchy. Headless UI Menu is single-level, action-only. Consumers needing toggle-state items or nested menus must escalate to Radix DropdownMenu or author bespoke wiring. 3. Group/separator semantic gaps — MenuSection renders no role="group"; MenuSeparator renders no role="separator". Both are layout-only without the ARIA roles the canonical anatomy requires for SR scoping. 4. Extended API — anchor-based floating positioning (backed by Floating UI), transition lifecycle hooks, and static/unmount lifecycle overrides add API surface with no canonical equivalent. The modal=true default on MenuItems (scroll locking + background inerting) is stricter than the canonical menu pattern warrants for context menus. The ARIA contract for the core case is correctly implemented: MenuItems renders role="menu", MenuItem renders role="menuitem", aria-disabled is used for disabled items (not DOM removal), and typeahead (A-Z) is present. aria-activedescendant is wired for keyboard focus tracking.
DropdownMenu import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
<DropdownMenu.Root> <DropdownMenu.Trigger asChild> <button>Open menu</button> </DropdownMenu.Trigger>
<DropdownMenu.Portal> <DropdownMenu.Content sideOffset={4}>
<DropdownMenu.Item onSelect={() => console.log('new-tab')}> Open in new tab </DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.CheckboxItem checked={bookmarked} onCheckedChange={setBookmarked} > <DropdownMenu.ItemIndicator>✓</DropdownMenu.ItemIndicator> Bookmarked </DropdownMenu.CheckboxItem>
<DropdownMenu.Separator />
<DropdownMenu.RadioGroup value={sort} onValueChange={setSort}> <DropdownMenu.Label>Sort by</DropdownMenu.Label> <DropdownMenu.RadioItem value="name"> <DropdownMenu.ItemIndicator>●</DropdownMenu.ItemIndicator> Name </DropdownMenu.RadioItem> <DropdownMenu.RadioItem value="date"> <DropdownMenu.ItemIndicator>●</DropdownMenu.ItemIndicator> Date </DropdownMenu.RadioItem> </DropdownMenu.RadioGroup>
<DropdownMenu.Separator />
<DropdownMenu.Sub> <DropdownMenu.SubTrigger>More options</DropdownMenu.SubTrigger> <DropdownMenu.Portal> <DropdownMenu.SubContent> <DropdownMenu.Item onSelect={() => console.log('copy-link')}> Copy link </DropdownMenu.Item> </DropdownMenu.SubContent> </DropdownMenu.Portal> </DropdownMenu.Sub>
<DropdownMenu.Arrow /> </DropdownMenu.Content> </DropdownMenu.Portal></DropdownMenu.Root>Divergence
| From | Type | → To | Rationale |
|---|---|---|---|
anatomy[root] | reshaped | DropdownMenu.Root + DropdownMenu.Portal + DropdownMenu.Content | The canonical `root` slot is the single `role="menu"` surface. Radix DropdownMenu splits this across three components: Root owns controlled- open state and the `onOpenChange` callback; Portal handles DOM relocation so the menu escapes stacking contexts (z-index / overflow issues); Content owns `role="menu"` and all positioning / keyboard logic. A consumer must compose all three to replicate the canonical root slot. This matches the Radix pattern established across Dialog, Popover, and Select. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu |
anatomy[root] | extended | + DropdownMenu.Content exposes `loop` (boolean, default false) — when true, keyboard focus wraps from last item back to first and vice versa. Canonical `wrapNavigation` maps to this prop. Also exposes `forceMount` (boolean) to keep the content in the DOM while closed (useful for CSS exit animations). Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#content | The canonical root slot does not model portal-level forceMount or the `loop` / no-loop toggle at the content level. Radix exposes both because Content is the primary mounting boundary and keyboard-focus container. |
anatomy[menuitem] | renamed | DropdownMenu.Item | Same interactive role (`role="menuitem"`). Radix uses "Item" as its compound-component term across all collection primitives (Select, Tabs, ContextMenu). The `onSelect` callback is Item's primary interaction event (fires on Enter / Space / click). The `disabled` prop sets both HTML `disabled` and `aria-disabled="true"` on the underlying element; disabled items remain in the DOM and in the roving-tabindex sequence. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#item |
anatomy[menuitemcheckbox] | renamed | DropdownMenu.CheckboxItem | Same role (`role="menuitemcheckbox"`) with `aria-checked` wired from the `checked` prop. Radix uses "CheckboxItem" as the compound name. The `checked` prop accepts `boolean | 'indeterminate'` matching the canonical `aria-checked="true|false|mixed"` contract. The `onCheckedChange` callback fires on toggle; the menu does not close on activation by default for checkbox items (mirrors canonical closeOnSelect=false for toggle items). Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#checkboxitem |
anatomy[menuitemradio] | renamed | DropdownMenu.RadioItem | Same role (`role="menuitemradio"`) with `aria-checked` derived from the RadioGroup's `value` prop. RadioItem requires a `value` prop; RadioGroup owns `value` + `onValueChange` for controlled radio state. The canonical menuitemradio contract (one checked per group, mutually exclusive) is fully honoured. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#radioitem |
anatomy[group] | reshaped | DropdownMenu.Group (generic grouping) + DropdownMenu.RadioGroup (radio scoping) | Canonical `group` is a single `role="group"` slot used for both visual grouping and radio scoping. Radix splits into two components: Group (no required role assignment in docs — used for visual and keyboard organisation) and RadioGroup (wraps RadioItems with `value` + `onValueChange` for mutually-exclusive selection). RadioGroup does not explicitly expose `aria-labelledby`; consumers must add it if a visible label is needed for SR group context. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#radiogroup |
anatomy[separator] | renamed | DropdownMenu.Separator | Same role and semantic (`role="separator"`). Radix uses "Separator" as the compound name. Not focusable — the roving-tabindex skips Separator correctly. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#separator |
anatomy[submenu-trigger] | renamed | DropdownMenu.SubTrigger | Same concept — a menuitem that opens a nested menu. Radix uses SubTrigger (inside a Sub container). SubTrigger carries `role="menuitem"`, `aria-haspopup="menu"`, and `aria-expanded` managed by Radix automatically. The `textValue` prop provides the typeahead string when the trigger's text content alone is not suitable. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#subtrigger |
anatomy[submenu] | reshaped | DropdownMenu.Sub + DropdownMenu.SubContent | Canonical `submenu` is a single nested `role="menu"` slot. Radix splits it into Sub (the stateful container owning open/close lifecycle and aria-expanded wiring) and SubContent (the rendered surface with `role="menu"`). This mirrors the Root/Content split at the top level. SubContent accepts the same positioning props as Content (side, align, sideOffset, avoidCollisions). Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#sub |
anatomy[icon] | omitted | — | Radix Item has no dedicated icon sub-component. Leading icons are authored as free children (e.g. a plain `<svg aria-hidden="true">`) inside DropdownMenu.Item; Radix applies no special slot wiring, accessibility handling, or decorative marker. The canonical icon slot's `aria-hidden` contract must be applied manually by the consumer. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#item |
anatomy[shortcut] | omitted | — | Radix Item has no shortcut sub-component and does not wire `aria-keyshortcuts` on the item element. Keyboard shortcut text is authored as a free child (`<kbd>`) inside Item; `aria-keyshortcuts` on the parent Item must be set manually by the consumer. The canonical shortcut slot's programmatic association is unimplemented at the Radix layer. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#item |
anatomy[root] | extended | + DropdownMenu.Label — a non-interactive, non-focusable element used as a visual group heading inside the menu. No `role` documented; renders as a `<div>` by default. Does not carry `aria-labelledby` from the containing Group or RadioGroup automatically — consumers must wire this if SR group context is required. DropdownMenu.ItemIndicator — renders only when the parent CheckboxItem or RadioItem is checked. Provides a convenient mounting point for the check glyph or radio-filled icon without needing conditional CSS; the canonical anatomy carries the checkmark as a design concern inside the menuitemcheckbox slot. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#itemindicator DropdownMenu.Arrow — an optional SVG arrow rendered inside Content to visually connect the menu to its Trigger. No canonical equivalent; purely a presentational enhancement. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#arrow | Radix extends the canonical anatomy with three presentational/convenience sub-components not present in the canon: Label (group heading), ItemIndicator (checked-state glyph mount), and Arrow (positioning caret). All are optional. |
axes.variants[horizontal] | omitted | — | DropdownMenu.Content exposes `[data-orientation]` reflecting "vertical" or "horizontal" but does not accept an `orientation` prop — the orientation is always vertical for dropdown menus (the keyboard model uses ArrowDown/ArrowUp). The canonical `horizontal` variant (used for menubar-style surfaces) is not supported by DropdownMenu. Radix ships a separate Menubar primitive (`@radix-ui/react-menubar`) for `role="menubar"` + horizontal keyboard navigation; that is a distinct package and component, not a variant of DropdownMenu. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#content |
axes.properties[size] | omitted | — | Radix DropdownMenu is an unstyled primitive and ships no `size` prop. Width, padding, and typographic scale of items are entirely consumer- controlled via CSS classNames on Content and Item. The canonical `sm | md` size axis is a design-system convention layered above the primitive. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu |
axes.properties[typeahead] | reshaped | Always-on typeahead via Item's `textValue` prop; no explicit boolean toggle on Content. Each Item's typeahead string defaults to its text content; `textValue` overrides it for items with non-text children (icons, custom renderers). | The canonical `typeahead` property is a boolean toggle. Radix implements typeahead as a non-negotiable always-on behaviour (matches canonical default of `true`). There is no opt-out prop on Content to disable typeahead. Consumers that need to disable typeahead in Radix must suppress `textValue` and ensure items have no text-node children — an indirect workaround, not a first-class option. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#item |
axes.properties[wrapNavigation] | renamed | loop prop on DropdownMenu.Content (boolean, default: false) | Canonical `wrapNavigation` (default true — wrap is on) maps to Radix's `loop` prop on Content. The defaults are inverted: canonical defaults to wrapping on, Radix defaults to wrapping off (`loop={false}`). Consumers who want the canonical default behaviour must explicitly pass `loop={true}` to DropdownMenu.Content. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#content |
axes.properties[closeOnSelect] | reshaped | Per-item `onSelect` callback — calling `event.preventDefault()` inside `onSelect` on an Item suppresses close. CheckboxItem and RadioItem do not close the menu on activation by default (no `event.preventDefault()` needed). No top-level boolean toggle on Content. | Canonical models `closeOnSelect` as a single boolean that covers all items. Radix exposes per-item control: action Items close on activation by default (preventing close requires `event.preventDefault()` in `onSelect`); CheckboxItem and RadioItem never close the menu on activation. This per-item granularity is more flexible but does not map to a single top-level boolean. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#item |
events[itemActivate] | renamed | onSelect callback on DropdownMenu.Item (signature: event => void) | Canonical `itemActivate` fires when a menuitem is activated. Radix's per-item `onSelect(event: Event)` is the direct equivalent. The canonical payload `{ itemId, originalEvent }` does not exist as a structured object; Radix passes the raw DOM Event. Consumer maps `event.currentTarget` to item identity. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#item |
events[valueChange] | reshaped | onCheckedChange on CheckboxItem (signature: checked => void) + onValueChange on RadioGroup (signature: value => void) | Canonical `valueChange` is a single event covering both checkbox-toggle and radio-selection changes, with a `groupId` field to identify radio groups. Radix splits into two separate callbacks: `onCheckedChange` per CheckboxItem for boolean toggle, and `onValueChange` on RadioGroup for string radio selection. The canonical unified payload `{ itemId, checked, groupId }` must be reconstructed by the consumer from the two separate event surfaces. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#checkboxitem |
events[submenuToggle] | renamed | onOpenChange on DropdownMenu.Sub (signature: open => void) | Canonical `submenuToggle` with payload `{ submenuId, isOpen }` maps to Radix's `onOpenChange(open: boolean)` on Sub. The `submenuId` field from the canonical payload is not provided; consumers must track submenu identity from the React component tree context. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#sub |
events[itemActivate] | extended | + DropdownMenu.Root exposes `onOpenChange(open: boolean)` — fires whenever the top-level menu opens or closes. The canonical menu anatomy does not model this as a named event because the standalone `menu` primitive does not own a trigger; DropdownMenu bundles the trigger, so open/close is an observable lifecycle event. Source: https://www.radix-ui.com/primitives/docs/components/dropdown-menu#root | The canonical `menu` component is a standalone surface without a built-in trigger — open/close lifecycle is the parent's concern. DropdownMenu combines trigger + menu, making `onOpenChange` a natural extension at the Root level. |
anatomy[root] | extended | + Radix ships two additional packages that cover related canonical menu use-cases: `@radix-ui/react-context-menu` (right-click / long-press triggered menu) and `@radix-ui/react-menubar` (horizontal `role="menubar"` surface). Both share the same sub-component vocabulary (Item, CheckboxItem, RadioItem, Group, etc.) but with context-appropriate Root/Trigger wrappers. The canonical `menu` audit focuses on DropdownMenu as the primary button-triggered menu primitive. | The canonical `menu` description notes composability as context-menu, embedded surface, or dropdown — Radix splits these into three packages. ContextMenu and Menubar are separate audits if needed; all divergence rows in this file describe DropdownMenu behaviour. |
Why this audit reads the way it does
Radix DropdownMenu is a low-level unstyled primitive that fully implements the APG Menu Button pattern: role="menu" on Content, role="menuitem" on Items, role="menuitemcheckbox" on CheckboxItem, role="menuitemradio" on RadioItem, aria-haspopup + aria-expanded on SubTrigger, roving tabindex, and always-on typeahead. It diverges from the canonical standalone `menu` anatomy in three structural ways. First, the canonical single `root` slot is spread across Root + Portal + Content — the split is a deliberate Radix pattern to separate state, DOM placement, and rendered surface. Second, the canonical `horizontal` variant is absent — DropdownMenu is always vertical; horizontal menus require the separate `@radix-ui/react-menubar` package. Third, the canonical top-level boolean props (`typeahead`, `wrapNavigation`, `closeOnSelect`) are reshaped: typeahead is always-on, wrapNavigation becomes Content's `loop` prop (with an inverted default), and closeOnSelect is per-item `event.preventDefault()` in `onSelect`. Radix extends the canonical anatomy with Label (group heading), ItemIndicator (checked-state glyph), and Arrow (positioning caret) — all optional presentational additions. Events are reshaped from canonical unified payloads into per-sub-component callbacks; consumers must synthesise canonical payload shapes from multiple Radix event surfaces. The overall assessment: Radix DropdownMenu achieves full APG keyboard-model compliance and correct ARIA role hierarchy; the canonical design-system conventions (size scale, orientation variant, unified event payloads, boolean feature toggles) are absent as expected for an unstyled primitive.
Menu import { MenuTrigger, Menu, MenuItem, MenuSection, SubmenuTrigger, Header, Separator, Keyboard, Button, Popover, Text,} from 'react-aria-components';
// Basic action menu (no selection)<MenuTrigger> <Button aria-label="Actions">Open menu</Button> <Popover> <Menu onAction={(key) => console.log(key)}> <MenuItem id="new">New file</MenuItem> <MenuItem id="open">Open</MenuItem> <Separator /> <MenuItem id="save"> Save <Keyboard>⌘S</Keyboard> </MenuItem> <MenuItem id="save-as"> Save As… <Keyboard>⌘⇧S</Keyboard> </MenuItem> <Separator /> <MenuItem id="delete" isDisabled>Delete</MenuItem> </Menu> </Popover></MenuTrigger>
// Single-select (menuitemradio)<Menu selectionMode="single" defaultSelectedKeys={['date']}> <MenuSection aria-label="Sort by"> <MenuItem id="name">Name</MenuItem> <MenuItem id="date">Date modified</MenuItem> <MenuItem id="size">Size</MenuItem> </MenuSection></Menu>
// Multi-select (menuitemcheckbox)<Menu selectionMode="multiple" defaultSelectedKeys={['line-numbers']}> <MenuSection> <Header>View options</Header> <MenuItem id="line-numbers">Show line numbers</MenuItem> <MenuItem id="wrap">Wrap long lines</MenuItem> </MenuSection></Menu>
// Submenu via SubmenuTrigger<MenuTrigger> <Button>File</Button> <Popover> <Menu onAction={(key) => console.log(key)}> <MenuItem id="new">New</MenuItem> <SubmenuTrigger> <MenuItem id="share">Share</MenuItem> <Popover> <Menu> <MenuItem id="email">Email</MenuItem> <MenuItem id="link">Copy link</MenuItem> </Menu> </Popover> </SubmenuTrigger> </Menu> </Popover></MenuTrigger>Divergence
| From | Type | → To | Rationale |
|---|---|---|---|
anatomy[menuitemcheckbox] | reshaped | MenuItem with selectionMode="multiple" on the parent Menu or MenuSection — React Aria automatically applies role="menuitemcheckbox" and aria-checked to each MenuItem when the enclosing Menu/MenuSection has selectionMode="multiple". There is no separate sub-component for toggle items. | The canonical documents `menuitemcheckbox` as a distinct anatomy slot — a separate item type the author explicitly chooses. React Aria collapses the choice into the `selectionMode` prop on the container: authors declare the collection's selection semantics once, and the library derives the correct ARIA roles for every item. This unifies the API surface (one MenuItem sub-component handles all three item roles) at the cost of losing the explicit per-item role declaration the canonical anatomy provides. The AT-visible contract (role="menuitemcheckbox", aria-checked) is still produced correctly. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
anatomy[menuitemradio] | reshaped | MenuItem with selectionMode="single" on the parent Menu or MenuSection — React Aria automatically applies role="menuitemradio" and aria-checked to each MenuItem when the enclosing container has selectionMode="single". No separate RadioGroup wrapper is required; MenuSection with aria-label provides the group scope. | Same container-driven role derivation as menuitemcheckbox. The canonical requires an explicit `role="group"` container with `aria-labelledby` for radio groups; React Aria satisfies this via MenuSection (which renders `role="group"`) with either a `<Header>` child or an `aria-label` prop. The grouping contract is met, but the author never writes `menuitemradio` explicitly — the selectionMode value drives it. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
anatomy[group] | renamed | MenuSection | The canonical `group` slot maps to `role="group"` with `aria-labelledby`. React Aria names the sub-component `MenuSection`, which renders `role="group"`. The accessible name is provided either by a `<Header>` child (React Aria wires the labelledby relationship automatically) or by an explicit `aria-label` prop — the docs mandate one of the two. Functionally equivalent to the canonical group slot; the API name differs. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
anatomy[submenu-trigger] | reshaped | SubmenuTrigger wrapper component that takes a MenuItem (trigger) as its first child and a Popover+Menu (submenu) as its second child. The trigger MenuItem automatically receives aria-haspopup="menu" and aria-expanded. No separate sub-component named "submenu-trigger" exists. | The canonical documents `submenu-trigger` as a distinct anatomy slot — a menuitem variant with trailing chevron that carries aria-haspopup and aria-expanded. React Aria encapsulates the trigger/submenu pair inside `<SubmenuTrigger>` — the inner MenuItem gains the correct ARIA attributes automatically; the author does not set aria-haspopup or aria-expanded manually. This reduces boilerplate but means the trigger item is not a named slot the author selects independently; it is inferred from being the first child of SubmenuTrigger. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
anatomy[submenu] | reshaped | Nested Popover+Menu inside SubmenuTrigger. The submenu is a full Menu instance (same component as the root menu) wrapped in a Popover for floating positioning, placed as the second child of SubmenuTrigger. | The canonical documents `submenu` as a distinct structural slot anchored to the submenu-trigger. React Aria realises the submenu as a Popover+Menu pair — reusing the same Popover sub-component used for the root menu trigger, ensuring consistent floating-ui positioning and portal mechanics. The Popover wraps the Menu for positioning; the Menu carries role="menu" and the APG keyboard model. The pair is co-required inside SubmenuTrigger. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
anatomy[shortcut] | renamed | Keyboard | The canonical `shortcut` slot is named to match its semantic purpose (keyboard-shortcut text trailing the menuitem label). React Aria ships a `<Keyboard>` sub-component (imported from `react-aria-components`) that renders in the "end" grid area of the MenuItem. The canonical recommends rendering shortcut text inside `<kbd>` and wiring aria-keyshortcuts on the parent; the React Aria `<Keyboard>` component handles the visual positioning but does not automatically set aria-keyshortcuts — consumers must add that attribute to the MenuItem if needed. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
axes.variants[horizontal] | omitted | — | React Aria Menu has no `orientation` prop and no horizontal-menu variant. The APG documents horizontal menus (for menubar-style surfaces) as a valid aria-orientation="horizontal" variant; React Aria does not implement this. Horizontal menu surfaces require a separate MenuBar component (not yet shipped as of 2026-05-31 in react-aria-components) or a consumer-built composition. The canonical `horizontal` variant is entirely absent from the React Aria Menu API. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
axes.properties[size] | omitted | — | React Aria Menu ships no `size` prop. The library is unstyled by design; size distinctions (sm / md) are a consumer responsibility expressed via CSS custom properties or class names. ADR-001 notes that React Aria decouples behaviour from visual sizing throughout its API surface. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
axes.properties[typeahead] | omitted | — | React Aria Menu always enables typeahead — there is no boolean prop to disable it. The `textValue` prop on MenuItem supplies the accessible name used for typeahead matching when the item content is not plain text. The canonical `typeahead` property (a positive-default boolean the consumer can flip off) has no equivalent; typeahead is non-negotiable in the React Aria implementation. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
axes.properties[wrapNavigation] | renamed | shouldFocusWrap | The canonical `wrapNavigation: boolean` (positive-default) corresponds exactly to React Aria's `shouldFocusWrap: boolean` on the `<Menu>` component. Both control whether ArrowDown on the last item wraps to first and ArrowUp on the first wraps to last. The concept is one-to-one; only the prop name differs. React Aria's should* prefix is consistent across its collection components. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
axes.properties[closeOnSelect] | renamed | shouldCloseOnSelect | The canonical `closeOnSelect: boolean` maps directly to React Aria's `shouldCloseOnSelect: boolean` on `<Menu>`. Both control whether the menu closes after a MenuItem is activated. The should* prefix is React Aria's naming convention for boolean behaviour props; the underlying semantics are identical. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
axes.properties | extended | + `selectionMode: 'none' | 'single' | 'multiple'` on `<Menu>` and `<MenuSection>`. Controls ARIA role derivation for items (`menuitem` / `menuitemradio` / `menuitemcheckbox`), visual selection indicators, and whether `onSelectionChange` fires. Per-section override is supported, allowing mixed selection semantics within one menu. | The canonical anatomy documents `menuitem`, `menuitemcheckbox`, and `menuitemradio` as separate slots the author composes explicitly. React Aria unifies all three under a single `MenuItem` sub-component and derives the role from `selectionMode`, consistent with its ListBox / GridList convention. This enables runtime selection-mode switching without restructuring the compound tree. The canonical has no equivalent prop; the role choice is structural (distinct slots) rather than a runtime property. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
axes.properties | extended | + `trigger: 'press' | 'longPress'` on `<MenuTrigger>`. Controls the gesture required to open the menu from the trigger button. `'press'` (default) opens on normal click/tap/Enter; `'longPress'` opens on long-press (useful for secondary-action menus on mobile, e.g. context menus behind a press-and-hold gesture). Keyboard: Alt+ArrowDown opens a longPress menu. | The canonical Menu documents the menu surface itself without specifying the trigger gesture — MenuButton (a distinct canonical pattern) handles trigger mechanics. React Aria ships MenuTrigger as a co-required wrapper around the Menu, so trigger-gesture control lands on the same package. The canonical has no equivalent axis; the longPress mode is a mobile UX affordance not addressed in the canonical anatomy. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
axes.properties | extended | + `delay: number` (default `200`) on `<SubmenuTrigger>`. Controls the hover dwell time in milliseconds before the submenu opens when the user hovers over the submenu-trigger MenuItem. Zero disables the delay (immediate open on hover). Does not affect keyboard navigation (ArrowRight opens the submenu immediately regardless of delay). | The canonical submenu-trigger slot documents ArrowRight / Enter / Space as the open triggers but does not specify hover-dwell behaviour or expose it as a configurable axis. React Aria adds `delay` because the 200ms default is important for accessibility — it prevents accidental submenu opening while the pointer passes through the trigger item on the way to another item. The canonical does not address pointer-hover dwell timing. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
axes.properties | extended | + `escapeKeyBehavior: 'clearSelection' | 'none'` on `<Menu>`. Controls whether pressing Escape clears the current selection before closing the menu. Default `'clearSelection'` resets `selectedKeys` to empty on Escape; `'none'` preserves the selection and only closes the menu. | The canonical keyboard contract documents Escape as "closes the menu and returns focus to the trigger" without addressing selection-clearing semantics. React Aria surfaces this trade-off explicitly because menus used as persistent selection controls (multi-select filter menus) benefit from preserving selection across close/re-open cycles. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
events[itemActivate] | renamed | onAction(key) | The canonical `itemActivate` event (payload `{ itemId, originalEvent }`) maps to React Aria's `onAction(key: Key)` callback, available on both `<Menu>` (fires for any item) and `<MenuItem>` (fires for that specific item only). React Aria follows the on* callback convention. The payload is the item's `id` key rather than a full event object; the originalEvent discriminator is not exposed. The canonical `itemId` maps to the React Aria `key`. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
events[valueChange] | renamed | onSelectionChange | The canonical `valueChange` event (payload `{ itemId, checked, groupId }`) covers both menuitemcheckbox toggles and menuitemradio activations. React Aria surfaces this as `onSelectionChange(keys: Selection)` on `<Menu>` or per `<MenuSection>`. The payload is a `Selection` object (a Set-like of selected keys) rather than an individual checked-state delta — callers derive the checked state from the new selection set. The `groupId` concept is absent because React Aria scopes selection per MenuSection automatically. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
events[submenuToggle] | omitted | — | React Aria Menu exposes no discrete per-submenu open/close event analogous to the canonical `submenuToggle` (payload `{ submenuId, isOpen }`). The top-level `onOpenChange` on `<MenuTrigger>` fires for the root menu only. Individual submenu open/close state is managed internally by `SubmenuTrigger` with no consumer-observable event emitted. Consumers needing submenu-toggle analytics must use the `isDisabled` or controlled `isOpen` pattern on the inner Popover. Source: https://react-aria.adobe.com/Menu (fetched 2026-05-31) |
Why this audit reads the way it does
React Aria Menu is a behaviour-only compound primitive that owns the full APG keyboard contract (typeahead, roving-tabindex, submenu ArrowRight/ArrowLeft lifecycle, Home/End, Escape) while leaving visual styling entirely to the consumer. The three deepest structural divergences from the canon are: 1. Role derivation from selectionMode rather than distinct slots — the canonical documents menuitemcheckbox and menuitemradio as separate anatomy slots the author selects explicitly. React Aria derives the ARIA role from the container's selectionMode prop, collapsing the three item types into one MenuItem sub-component. The AT-visible contract is correct; the authoring model is fundamentally different. 2. Horizontal menu variant absent — React Aria Menu has no orientation prop and no horizontal-menu implementation. The APG documents horizontal menus as a valid pattern (aria-orientation="horizontal" with ArrowLeft/ArrowRight navigation); React Aria does not ship it. 3. SubmenuTrigger wraps trigger + submenu together — the canonical treats submenu-trigger and submenu as paired but separate anatomy slots. React Aria encapsulates both inside a single SubmenuTrigger wrapper, which automatically wires aria-haspopup and aria-expanded on the inner MenuItem. The ARIA contract is correct; the structural split is absent. The rename surface (shouldFocusWrap, shouldCloseOnSelect, onAction, onSelectionChange, isDisabled) follows React Aria's should*/on*/is* naming scheme uniformly applied across the library. The library does not expose typeahead as a configurable prop — it is always active.
Figma↔Code mismatches
Where designer and developer worlds typically misalign on this component.
- 01 Figma
Menu items drawn as styled list-items with no role-distinction
Code`role="menuitem"` plus roving-tabindex bookkeeping per item
ConsequenceDesigners compose menus as visually styled rows in a surface frame; developers must add `role="menuitem"` to each item plus the roving-tabindex bookkeeping (one menuitem at a time has `tabindex="0"`). Without explicit anatomy, the role and tab-stop semantics may ship as plain `<li>` with no role — AT users hear "list, list-item" instead of "menu, menuitem", and the canonical menu-keyboard model is absent.
CorrectDocument each item type as a distinct interactive slot — menuitem, menuitemcheckbox, menuitemradio, submenu-trigger. Figma carries a Menu Item component with role-variants (Action / Checkbox / Radio / Submenu). Code wires the role per anatomy slot plus the roving-tabindex on the parent menu container.
- 02 Figma
Submenu drawn as separate flow with no parent-child relationship
CodeNested `role="menu"` anchored to a submenu-trigger with `aria-haspopup` plus `aria-expanded`
ConsequenceDesigners compose submenus as separate menu surfaces that visually flyout from a parent item; developers ship the nested role hierarchy (parent menuitem with `aria-haspopup="menu"`, submenu with `role="menu"`, submenu's `aria-labelledby` pointing to the trigger). Without explicit anatomy, the parent-child relationship is implicit in the design file — implementations may ship the submenu as a `role="group"` or as another `role="dialog"` (Popover- style), losing the canonical submenu keyboard model.
CorrectDocument the submenu-trigger and submenu as paired slots with the explicit `aria-haspopup` plus `aria-expanded` contract. Figma carries a Submenu Trigger variant of menuitem (with trailing chevron) and a Submenu component instance with anchor-positioning to the trigger. Code wires `aria-haspopup="menu"` plus `aria-expanded` on the trigger; the submenu carries `role="menu"` plus `aria-labelledby` pointing to the trigger element's id.
- 03 Figma
Separator drawn as horizontal line with no role
Code`role="separator"` (or CSS-only border) — never focusable
ConsequenceDesigners draw separators as horizontal lines between groups; developers ship either `<li role="separator">` (with the role) or a CSS border (no role). Implementations may accidentally make separators focusable (extra `tabindex="0"` on the `<li>` element) — keyboard users encounter a tab stop on the separator with no clear behaviour.
CorrectDocument separator as a decorative slot with explicit "not focusable" in the a11y hint. Figma carries the separator as a non-interactive component instance; the design surface signals "this is visual scaffolding". Code uses `role="separator"` with no `tabindex` AND with explicit `aria-orientation` for vertical separators in horizontal menus. The roving-tabindex algorithm skips separators when arrow keys navigate.
- 04 Figma
Menuitemcheckbox drawn as plain item with check icon
Code`role="menuitemcheckbox"` plus `aria-checked="true|false|mixed"` (icon decorative)
ConsequenceDesigners compose toggle items as plain menuitems with a leading checkmark glyph when checked; developers must distinguish the role (`menuitemcheckbox` vs `menuitem`) and wire `aria-checked` for AT. Without anatomy guidance, the role distinction may ship as plain `menuitem` with the visual checkmark — sighted users see the state, AT users hear no toggle semantic.
CorrectDocument menuitemcheckbox and menuitemradio as distinct slots from menuitem. Figma carries explicit Checkbox-Item and Radio-Item variants with checked / unchecked states. Code wires `role="menuitemcheckbox"` plus `aria-checked="${state}"` (with the icon `aria-hidden="true"`). The icon and the `aria-checked` attribute share the same source-of-truth.
- 05 Figma
Keyboard shortcut drawn as decorative trailing text
Code`<kbd>` text plus `aria-keyshortcuts` on the parent menuitem
ConsequenceDesigners ship keyboard shortcuts as visible trailing text ("Ctrl+S"); developers must wire `aria-keyshortcuts` on the menuitem so AT users hear the shortcut. Without explicit anatomy, the shortcut may ship as plain text only — AT users hear "Save" without "Control+S", losing the programmatic association the shortcut needs to surface in dedicated SR-shortcut listings.
CorrectDocument the shortcut slot as a content slot paired with `aria-keyshortcuts` on the menuitem. Figma carries the shortcut text as a child of the menuitem with explicit annotation indicating the parent's `aria-keyshortcuts` value. Code renders the text inside `<kbd>` and sets `aria-keyshortcuts="Control+S"` on the parent menuitem. The shortcut shows up both in the visible menu AND in AT shortcut-listing surfaces.
Variants, properties, states
Variants
Structurally different versions of the component.
vertical horizontal Properties
The same component, parameterised.
| Property | Type |
|---|---|
size | sm | md |
typeahead | boolean |
wrapNavigation | boolean |
closeOnSelect | boolean |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visibleactivedisabled |
data | idleitemFocusedtypeaheadActivesubmenuOpen |
Figma ↔ Code property map
| Figma | Kind | Code | Notes |
|---|---|---|---|
Variant | Enum | variant | Maps vertical / horizontal orientation; reflects to `aria-orientation`. |
Size | Enum | size | sm / md. |
Typeahead | Boolean | typeahead | Positive-default boolean; when true (default) printable-character keystrokes buffer and focus jumps to matching menuitem. |
Wrap Navigation | Boolean | wrapNavigation | Positive-default boolean; when true (default) ArrowDown on last item wraps to first, ArrowUp on first wraps to last. |
Close On Select | Boolean | closeOnSelect | Positive-default boolean for action menuitems; when true (default) activating a menuitem closes the menu. menuitemcheckbox / menuitemradio typically opt out (closeOnSelect false). |
Aria Label | Text | ariaLabel | Accessible name for the menu. One of ariaLabel / ariaLabelledBy is required. |
Aria Labelledby | Text | ariaLabelledBy | Id of element naming the menu (typically the trigger or a heading sibling). One of ariaLabel / ariaLabelledBy is required. |
Items | Slot | items | Repeatable slot for menuitem / menuitemcheckbox / menuitemradio / separator / submenu-trigger children. |
State transitions
| From | To | Trigger |
|---|---|---|
idle | itemFocused | User opens the menu or activates a menuitem via arrow-key navigation |
itemFocused | typeaheadActive | User types a printable character; typeahead-buffer accumulates within ~500ms |
typeaheadActive | itemFocused | Typeahead-buffer timeout (~500ms) or matching menuitem becomes focused |
itemFocused | submenuOpen | User activates a submenu-trigger via Enter / Space / ArrowRight |
submenuOpen | itemFocused | User dismisses submenu via ArrowLeft / Escape |
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
root | frame | Menu surface frame; vertical / horizontal variant per orientation property |
menuitem | instance | Menuitem instance with default / hover / focus-visible / disabled / pressed variants |
menuitemcheckbox | instance | Menuitemcheckbox instance with checked / unchecked / mixed variants |
menuitemradio | instance | Menuitemradio instance with checked / unchecked variants; grouped under group slot |
group | frame | Group container with optional heading; visual indent or background treatment |
separator | instance | Horizontal divider line; muted color; not a button or interactive variant |
submenu-trigger | instance | Menuitem variant with trailing chevron-right icon; expanded state per submenu open |
submenu | instance | Submenu instance — same as root menu but anchored to submenu-trigger |
icon | instance | Icon instance at inline-start of menuitem; muted color |
shortcut | text | Keyboard shortcut text style; muted color; trailing menuitem label |
Code anatomy
| Slot | Code slot | Semantic |
|---|---|---|
root | root | ul-or-div-with-role-menu |
menuitem | menuitem | li-or-div-with-role-menuitem |
menuitemcheckbox | menuitemcheckbox | li-or-div-with-role-menuitemcheckbox |
menuitemradio | menuitemradio | li-or-div-with-role-menuitemradio |
group | group | div-with-role-group |
separator | separator | li-or-div-with-role-separator |
submenu-trigger | submenu-trigger | li-with-aria-haspopup-menu |
submenu | submenu | ul-with-role-menu |
icon | icon | presentational |
shortcut | shortcut | kbd-or-span |
Cross-framework expression
| Framework | Structure mechanism | Variant mechanism |
|---|---|---|
| Web Components | A `<ui-menu>` host with `orientation`, `typeahead`, `wrap-navigation`, `close-on-select` attributes. Renders `role="menu"` shadow tree with slotted `<ui-menuitem>` / `<ui-menuitemcheckbox>` / `<ui-menuitemradio>` / `<ui-menu-separator>` / `<ui-submenu>` children. The host owns roving-tabindex bookkeeping plus the typeahead buffer. Wire-name `ui-menu` becomes `md-menu` / `mat-menu` / `pf-menu` per design system; canonical wire-name stays `menu`. | Attributes drive variant + properties; `orientation="vertical"` (default) / `"horizontal"` reflects to `aria-orientation`. Typeahead is a host- managed buffer that reset after ~500ms of inactivity. Submenu instances are slotted `<ui-submenu>` with their own `<ui-menuitem>` children; the host wires anchor-positioning relative to the submenu-trigger element. |
| React | Compound component (Radix Menu / DropdownMenu, React Aria Menu, Headless UI Menu, Mantine Menu, HeroUI Menu). Compound shape — `<Menu.Root>`, `<Menu.Item>`, `<Menu.Sub>`, `<Menu.SubTrigger>`, `<Menu.Separator>`, `<Menu.Group>`, `<Menu.RadioItem>`, `<Menu.CheckboxItem>`. Radix and React Aria are the most APG- compliant choices; both ship typeahead, arrow-key navigation, submenu lifecycle, and roving-tabindex out of the box. | Props with class-variance-authority for variant + size; `orientation: 'vertical' | 'horizontal'` drives the keyboard model. Compound subcomponents handle role wiring (CheckboxItem renders `role="menuitemcheckbox"` with `aria-checked`). Server-rendered menus hydrate to the interactive composition without flicker. |
| Angular (signals) | Angular Material's `MatMenu` ships a menu with arrow-key navigation, typeahead, and submenu support. Angular CDK provides lower-level overlay primitives for custom menus. Signal-driven open-state via `signal<boolean>('isOpen')`; menu items as `<button mat-menu-item>` children. | `[attr.role]="'menu'"` plus `[attr.aria-orientation]` reflective bindings; signal-derived `effect()` updates roving-tabindex on arrow-key events. Typeahead buffer as `signal<string>('typeahead')` with `effect()` resetting after 500ms. |
| Vue | PrimeVue Menu, Element Plus el-menu, Naive UI Dropdown / Menu, Vuetify v-menu. PrimeVue Menu is closest first-class APG-compliant Vue menu primitive. `defineProps` exposes `model` (array of menuitem objects with label / icon / shortcut / items[] / command callback), v-model:visible for open state. | `defineProps` with literal-union types (`orientation: 'vertical' | 'horizontal'`). Slot-based per-item rendering for customisation. Typeahead and submenu lifecycle implemented via composables (VueUse's useMagicKeys + custom typeahead-buffer composable for typeahead). |
Events
itemActivatevalueChangeoptionalsubmenuToggleoptional
Form integration
- name attribute
- Menu is an action-trigger affordance, not a form control. The `role="menu"` element has no `name` attribute and contributes nothing to FormData. Even `menuitemcheckbox` and `menuitemradio` items do not submit values — they trigger application state changes via the valueChange event; the consumer propagates state to forms or URL parameters.
Performance thresholds
typeaheadMatchLatencytime-to-focus≥16msEach character typed into the typeahead buffer must update the focused menuitem within one frame (16.67ms on a 60Hz display). For long menus (50+ items), the linear-scan implementation is fine; for very long menus (200+ items, file-history or recent-projects surfaces), pre-build a sorted-by-accessible- name index for sub-millisecond match.
submenuOpenLatencytime-to-render≥100msArrowRight / Enter activation on a submenu-trigger to the visible submenu plus focus on its first item must complete within 100ms for the submenu navigation to feel responsive. Above the threshold, users double-press (skipping the submenu open and activating the trigger again). Pre-mount submenu DOM with `aria-hidden="true"` plus `display: none` so the open is a state flip, not a mount.
itemListVirtualizationThresholditem-count≥200itemsAbove ~200 menuitems, the menu surface should virtualize (render only viewport-visible items). Common case: file-recent-history, emoji pickers, command palettes with hundreds of commands. Without virtualization, menu-open latency exceeds the 100ms budget due to the cumulative DOM-render cost.
Internationalisation
RTL · mirroring
Submenu flyout direction follows logical direction — submenus flyout to the inline-end, which is visual-left under RTL. Submenu-trigger chevron icons flip direction (chevron-right under LTR, chevron-left under RTL). ArrowRight / ArrowLeft tree-navigation map to visual direction per APG recommendation; under RTL, ArrowRight closes a submenu (visually moving toward parent at the right) and ArrowLeft opens. Typeahead matches against the user's locale-formatted accessible names (German "Ä" matches "Ä-prefixed" items, etc.).
Text expansion
Menuitem labels expand 30-50% under translation ("Save" → "Speichern" 75% longer; "Save As..." → "Speichern unter..." 100% longer). Long labels force wider menu surfaces; the canonical menu width follows the longest item's width plus padding. Keyboard shortcuts are stable (Ctrl / Cmd keynames translate but the rendered text "Ctrl+S" usually stays in English even in localised UIs). For localised typeahead, the buffer matches against the locale-translated accessible name, not the English source.
Accessibility
| Slot | Accessibility hint | |
|---|---|---|
root | `<ul role="menu" aria-orientation="vertical" aria-labelledby="menu-label-id">` (or `<div role="menu">` for non-list compositions). The `aria-orientation` attribute drives keyboard navigation — ArrowDown / ArrowUp for vertical; ArrowLeft / ArrowRight for horizontal. Accessible name is required: either `aria-label` or `aria-labelledby`. SR users hear "menu, ${label}, ${itemCount} items" on entry. | |
menuitem | `<li role="menuitem" tabindex="-1">` (or `tabindex="0"` for the active one). Accessible name is the menuitem's text content; for icon-led menuitems with no visible label, add `aria-label`. Disabled menuitems carry `aria-disabled="true"` AND retain DOM presence (do not remove from the menu — keeps the menu structure stable across state changes). | |
menuitemcheckbox | `<li role="menuitemcheckbox" aria-checked="true" tabindex="-1">`. SR announces "checked" / "unchecked" / "mixed" via `aria-checked`. Activating flips the state and fires valueChange — the menu typically does NOT close so users can toggle multiple options without re-opening. The visual checkmark glyph mirrors `aria-checked` from the same source-of-truth. | |
menuitemradio | `<li role="menuitemradio" aria-checked="true" tabindex="-1">` inside `<div role="group" aria-labelledby="group-label-id">`. Only one menuitemradio per group is checked at a time; SR announces the new selection on activation. The group's `aria-labelledby` provides context ("Sort by, Name selected"). | |
group | `<div role="group" aria-labelledby="group-label-id">` wrapping related menuitems. The group's heading is a sibling element with the matching id (typically a `<span>` or `<div>` with visible-or-sr-only text). For purely visual grouping without semantic meaning, omit `role="group"` and use a separator slot instead. | |
separator | `<li role="separator" aria-orientation="horizontal">` or simply a CSS-rendered border between groups. NOT focusable — no `tabindex`, no click handler. The roving-tabindex skips over separators when arrow keys navigate. Some SR / browser combinations announce "separator" on encounter; others skip it silently. Both are acceptable. | |
submenu-trigger | `<li role="menuitem" aria-haspopup="menu" aria-expanded="false" tabindex="-1">`. `aria-expanded` reflects the submenu's open state. Activation paths: Enter / Space (open submenu and focus first item), ArrowRight (open submenu and focus first item — the canonical APG path), click (open submenu, focus first item). The trailing chevron icon is decorative (`aria-hidden="true"`); the submenu existence comes from `aria-haspopup`. | |
submenu | `<ul role="menu" aria-labelledby="trigger-id">` — the submenu's accessible name comes from the triggering menuitem. Same roving-tabindex within the submenu. ArrowLeft on a top-level submenu item moves focus back to the trigger and closes the submenu; ArrowLeft on a nested-deeper submenu item moves to the parent submenu's trigger. Escape closes the entire submenu chain back to the root menu. | |
icon | `aria-hidden="true"`. The menuitem's text content carries the accessible name; the icon is visual scaffolding. For information-bearing icons (a sync-status indicator distinguishing "Sync now" from "Sync error"), surface the meaning in the menuitem's visible text or via `aria-label` on the menuitem itself, not via `aria-label` on the icon. | |
shortcut | Render as `<kbd>` for the visible text — the canonical HTML element for keyboard input. Set `aria-keyshortcuts="Control+S"` (or the canonical key-combination string per WAI-ARIA) on the parent menuitem so AT users hear the shortcut in the announcement. Avoid duplicating the shortcut in the menuitem's accessible name — `aria-keyshortcuts` is the canonical surface. |
Accessibility acceptance
Keyboard walk
| Keys | Expected |
|---|---|
Tab | Focus enters the menu at the first menuitem (or the most-recently-focused item from the prior visit). Tab inside the menu typically closes the menu (or moves focus out, depending on context — for context menus Tab closes; for embedded menus Tab moves to the next document focusable while keeping the menu visible). |
ArrowDown / ArrowUp (vertical orientation) | Moves focus to the next / previous menuitem, skipping separators. Wraps at boundaries per `wrapNavigation` property (default true). The roving-tabindex shifts. |
ArrowLeft / ArrowRight (horizontal orientation) | Same as ArrowDown / ArrowUp for horizontal menus. For horizontal menus with submenus, ArrowDown opens a vertical submenu beneath the focused item. |
ArrowRight (focused menuitem with submenu) | For vertical menus, opens the submenu and moves focus to the first item in the submenu. The submenu-trigger's `aria-expanded` flips to `true`. APG- canonical submenu-open keyboard. |
ArrowLeft (focused item in submenu) | Closes the submenu and returns focus to the submenu-trigger in the parent menu. For deeply nested submenus, ArrowLeft closes only the current submenu, not the entire chain. |
Enter / Space (focused menuitem) | Activates the menuitem — fires the itemActivate event. For action menuitems, the menu typically closes (per `closeOnSelect`). For menuitemcheckbox, toggles `aria-checked` without closing. For menuitemradio, sets the new selection within the radio group. |
Home / End | Home moves focus to the first menuitem; End to the last. The roving-tabindex shifts. |
Printable character (a–z, 0–9) | Typeahead — the character buffers (within ~500ms of inactivity) and focus jumps to the next menuitem whose accessible name starts with the buffered string. Multi- character buffers ("se") match accessible names starting with "se" before single- character matches. |
Escape | Closes the current menu (or current submenu, returning focus to the parent's submenu-trigger). For top-level menus, Escape closes the menu and returns focus to the trigger element (or the previously- focused element for context menus triggered by right-click). |
Screen-reader announcements
| Trigger | Expected |
|---|---|
| SR enters the menu | SR announces "menu, ${label}, ${itemCount} items" on entry — label from `aria-label` or `aria-labelledby`. The first menuitem then announces its own accessible name plus role ("menuitem, Save"). |
| SR navigates to a menuitemcheckbox | SR announces "${label}, menu item check box, ${checked|not checked|mixed}". The `aria-checked` value drives the state announcement. |
| SR navigates to a menuitemradio | SR announces "${label}, menu item radio, ${checked|not checked}, ${groupLabel}". The group's `aria-labelledby` provides the group context. |
| SR navigates to a submenu-trigger | SR announces "${label}, menu item, submenu, ${expanded|collapsed}". The `aria-haspopup` plus `aria-expanded` attributes drive the announcement. |
| User activates a submenu (ArrowRight) | SR announces the submenu open and the first item — "${submenuLabel}, menu, ${itemCount} items, ${firstItemLabel}, menu item". |
| User activates a menuitemcheckbox | SR announces the new state — "checked" or "not checked" — via `aria-checked` change. The menu does not close; the user can toggle multiple items in sequence. |
axe-core rules to assert
aria-allowed-attraria-allowed-rolearia-required-attraria-required-childrenaria-required-parentaria-valid-attr-valuecolor-contrast
Same data as JSON for direct ingestion into Playwright + @axe-core/playwright or Jest + jest-axe:
/api/components/menu/a11y-fixture.json
Contracts
Non-negotiable contracts
APGWAI-ARIA menu role and required children The menu carries `role="menu"` and every interactive child carries `role="menuitem"`, `role="menuitemcheckbox"`, or `role="menuitemradio"`. The role hierarchy is consistent — menu contains menuitems and groups; groups contain menuitemradios.
Without the role hierarchy, AT announces the root as a menu but children as plain list-items — SR users hear "menu, list, list-item, Save" instead of "menu, menuitem, Save". The canonical APG menu pattern is fundamentally broken without consistent roles.
APGAPG: Menu and menubar pattern keyboard interaction Arrow-key navigation moves focus between menuitems — ArrowDown / ArrowUp for vertical menus, ArrowLeft / ArrowRight for horizontal. Home / End jump to first / last. Tab does NOT navigate between items — Tab closes the menu (or moves focus out). The roving-tabindex maintains a single tab-stop.
Without the canonical keyboard model, keyboard users cannot navigate the menu efficiently — they must mouse-click each item. The arrow-key pattern is the defining keyboard affordance of menus; without it, the menu is a list-of-buttons with misleading `role="menu"`.
APGAPG: Menu pattern submenu interaction Submenus open via ArrowRight (or Enter / Space) on the submenu-trigger menuitem; close via ArrowLeft / Escape. The submenu-trigger carries `aria-haspopup="menu"` plus `aria-expanded` reflecting the submenu's open state.
Without the canonical submenu keyboard model, keyboard users cannot navigate nested menu hierarchies. Mouse-click-only submenu activation excludes keyboard + AT users from the canonical APG affordance.
APGAPG: Menu pattern keyboard support — first-character search Typeahead is canonical for menus with more than ~7 menuitems — printable characters buffer and move focus to the next menuitem whose accessible name starts with the buffered string. Reset buffer after ~500ms of inactivity.
Without typeahead, long menus require users to arrow through every item to reach a target. For file menus or large action lists, the friction makes the menu impractical for keyboard users. APG documents typeahead as canonical menu keyboard support; mature menu libraries ship it by default.
Canon Disabled menuitems retain DOM presence with `aria-disabled="true"` plus visually-disabled treatment. Removing disabled items from the menu (or hiding them entirely) breaks the stable-menu- structure expectation users build.
Without the rule, the menu's structure shifts between renders — users encounter different item counts and positions across state changes. The stable- structure discipline is canonical; disabled items remain visible and announced, just with the disabled state plus reason.
Vocabulary drift
- WAI-ARIA
role=menu + role=menuitem + role=menuitemcheckbox + role=menuitemradio- Canonical ARIA pattern. WAI-ARIA Authoring Practices Guide documents the full keyboard model — arrow-key navigation, typeahead, submenu lifecycle, roving-tabindex. The canonical anatomy mirrors APG directly.
- HTML
<menu>- HTML `<menu>` element exists but is deprecated for context-menus (Firefox removed support); repurposed as a list- type element similar to `<ul>` in HTML5. The canonical menu pattern uses `<ul role="menu">` or `<div role="menu">` rather than the bare `<menu>` element.
- Material 3
Menu (Angular Material `MatMenu`)- Material 3 ships Menu with arrow-key navigation, typeahead, submenu support out of the box. `MatMenu` (Angular Material) renders the canonical `role="menu"` with menuitem children plus the full APG keyboard model.
- Atlassian
DropdownMenu (combined trigger + menu)- Atlassian ships DropdownMenu (combined pattern with trigger button + menu surface) but no first-class standalone Menu primitive. For context menus or embedded menus, Atlassian projects compose Menu surfaces from lower-level primitives.
- Polaris
ActionList (menu-like) + Popover (host)- Polaris ships ActionList (a list-of- actions component) typically composed inside Popover for menu-like surfaces. ActionList does not strictly carry `role="menu"` — it uses `role="presentation"` with `role="menuitem"` on each item, a slight non-canonical divergence.
- Carbon
OverflowMenu (combined) + ContextMenu- Carbon ships OverflowMenu (combined trigger + menu, kebab-icon) and ContextMenu (right-click triggered). Both render canonical `role="menu"` surfaces. Carbon's ContextMenu is the closest standalone-menu primitive.
- Radix
Menu / DropdownMenu / ContextMenu- Radix splits the menu pattern into Menu (low-level primitive), DropdownMenu (combined trigger), and ContextMenu (right-click triggered). The standalone Menu surface composes directly. Compound shape (`<Menu.Item>`, `<Menu.Sub>`, `<Menu.SubTrigger>`) mirrors the canonical anatomy.
- React Aria
Menu + MenuTrigger- React Aria ships a Menu primitive plus a MenuTrigger composer. The Menu primitive renders the canonical `role="menu"` with menuitem children plus the full APG keyboard model (typeahead, submenu, roving-tabindex) out of the box.
- Headless UI
Menu (combined trigger + items)- Headless UI's Menu component is combined — a single Menu component renders the trigger button plus the items. No standalone `role="menu"` primitive without trigger. For standalone-menu use cases, Headless UI projects compose from lower-level primitives or escalate to Radix / React Aria.
Common mistakes
#menuitem-no-role
Items use `<li>` without `role="menuitem"` when root is `role="menu"`
The root carries `role="menu"` but children are bare `<li>` elements without the menuitem role. AT announces the root as a menu but the items inside as plain list-items — the role hierarchy breaks. SR users hear "menu, list, list-item" instead of "menu, menuitem". WAI-ARIA explicit requirement violation.
When the root is `role="menu"`, every interactive child MUST carry `role="menuitem"`, `role="menuitemcheckbox"`, or `role="menuitemradio"`. Non-interactive children (separators, group headings) carry `role="separator"` or `role="group"` respectively. The role hierarchy is consistent — menu contains menuitems and groups; groups contain menuitemradios.
#menu-submenu-no-aria-haspopup
Submenu trigger lacks `aria-haspopup="menu"`
A menuitem opens a submenu but the menuitem does not carry `aria-haspopup="menu"`. AT users do not hear that the item has a submenu — the affordance is invisible. Users discover it by activating the item (Enter / ArrowRight) and stumbling into the nested menu. The expected announcement "submenu" goes missing.
Set `aria-haspopup="menu"` on the submenu-trigger menuitem AND `aria-expanded="false"` (toggling to "true" when the submenu opens). SR announces "submenu, collapsed" / "submenu, expanded" alongside the menuitem's accessible name. The visible chevron icon mirrors the `aria-expanded` state from the same source-of- truth.
#menu-no-typeahead
Menu lacks typeahead-search support
The menu implements arrow-key navigation but no typeahead — users cannot type the first letter of a menuitem to jump to it. For menus with more than ~7 items (file menus, large action lists), keyboard users must arrow through every item to reach a target. The APG menu pattern documents typeahead as canonical for menus with more than a few items.
Implement typeahead — buffer printable characters typed within ~500ms; on each character, find the next menuitem whose accessible name starts with the buffer (case-insensitive); move focus to the match. Reset the buffer on timeout. APG documents this as the canonical menu-search pattern; mature menu libraries (Radix Menu, React Aria Menu, Headless UI Menu) ship it by default.
#menu-separator-as-menuitem
Separator is focusable in the roving-tabindex
A separator between menu groups is rendered as `<li>` with `tabindex` (or accidentally inherits the menu's roving-tabindex). Keyboard users land on the separator when arrow keys navigate; activation does nothing (or fires an unintended handler). The empty tab-stop is confusing.
Render separators as `<li role="separator">` (or a CSS-rendered border) without `tabindex`. The menu's roving-tabindex algorithm must filter out separators when arrow keys advance focus — ArrowDown on a menuitem-before-separator skips the separator and lands on the next menuitem. Most menu libraries handle this correctly out of the box; custom implementations need the explicit filter.
#menu-disabled-not-aria-disabled
Disabled menuitems are removed from the DOM or not announced
Disabled menuitems are either removed from the menu entirely (removing visual + AT presence) or visually-disabled without `aria-disabled="true"` (sighted users see disabled, AT users hear enabled). The two failure modes both break the stable-menu-structure expectation users build.
Disabled menuitems retain DOM presence with `aria-disabled="true"` plus visually-disabled treatment. The menuitem stays in the roving-tabindex (focusable) so users can land on it and hear "Save, dimmed" — the disabled state plus reason. Activation is suppressed via `event.preventDefault` in the handler. The stable structure helps users learn the menu layout; the announcement reveals why an item cannot be activated.
#menu-arrow-no-wrap
Arrow keys do not wrap at boundaries
ArrowDown on the last menuitem stops; ArrowUp on the first stops. Users discover the boundary by pressing the key twice; the menu feels stuck. Wrapping (last + ArrowDown → first; first + ArrowUp → last) makes long menus more navigable — users can return to the start without arrowing back through every item. Some menus opt out of wrapping deliberately; canonical APG default is wrap.
On ArrowDown from the last menuitem, move focus to the first; on ArrowUp from the first, move focus to the last. Home / End jump to first / last respectively. The roving-tabindex bookkeeping handles the wrap. Honor the `wrapNavigation` property — implementations that want hard-stop boundaries set `wrapNavigation: false`.