react-menu
An accessible, keyboard-friendly React menu library for dropdowns, context menus, and nested submenus.
Repository Health
Technical Analysis
@szhsin/react-menu is a React component library for building menus: dropdown menus, context menus, nested submenus, and menu-driven UI patterns like checkbox and radio menu items. It implements the WAI-ARIA menu authoring practices pattern directly, so keyboard navigation (arrow keys, Home/End, type-ahead), focus management, and screen-reader semantics come built in rather than being left to the consuming application to reimplement.
The library ships unstyled, exposing composable components (Menu, MenuItem, SubMenu, MenuButton, ControlledMenu, MenuRadioGroup, and more) plus hooks (useMenuState, useClick, useHover) so teams can either use the batteries-included Menu wrapper or drop down to ControlledMenu for fully custom state control, portaling, and positioning. It supports concurrent-rendering-safe React 18+ usage and server-side rendering via an initialMounted escape hatch, addressing a common pain point for menu libraries that assume client-only mounting.
Built for teams that need menu/dropdown interaction patterns without hand-rolling ARIA compliance and focus trapping, or without pulling in a full component-library dependency just for a menu.
What You Get
- A
Menucomponent paired withMenuButtonfor standard dropdown menus, plusControlledMenufor fully custom-controlled menu state and positioning SubMenusupport with unlimited nesting depth for multi-level dropdown and context menusMenuItem,FocusableItem,MenuRadioGroup, and checkbox/radio menu item types with WAI-ARIA-correct roles and keyboard behavior- Flexible positioning options (
direction,align,position,boundingBoxRef) that keep menus within the viewport, plus optional portal rendering todocument.body - Built-in transition/animation support via the
react-transition-statedependency, with state values (opening,open,closing,closed) exposed for custom styling - Server-side rendering support through an
initialMountedprop that renders menu content before first open
Common Use Cases
- Adding a dropdown navigation or action menu to a toolbar or button without hand-building keyboard and focus handling
- Building a right-click context menu for a canvas, table row, or file-manager-style UI
- Implementing settings or filter menus that use checkbox and radio menu items instead of a separate dropdown/select component
- Constructing deeply nested menu systems, such as an application menu bar or a multi-level command palette
- Adding accessible menu interaction to a design system or component library that otherwise ships unstyled primitives
Under The Hood
Architecture
The library is organized as a layered component tree: Menu (src/components/Menu.js) is a thin convenience wrapper that owns button-cloning and keyboard-open behavior, delegating actual rendering to ControlledMenu (src/components/ControlledMenu.js), which sets up React context providers (SettingsContext, EventHandlersContext) consumed by MenuList and, transitively, every MenuItem/SubMenu descendant. State transitions (opening/open/closing/closed) are delegated to the useMenuState/useMenuStateAndFocus hooks built on top of the external react-transition-state package rather than reimplemented locally. Positioning is isolated into its own positionUtils/ module (positionMenu.js dispatching to placeLeftorRight.js/placeToporBottom.js), a clean separation of layout math from component logic. Portal rendering (createPortal) is handled conditionally in ControlledMenu, and MenuButton/instanceRef plumbing via useImperativeHandle lets parent code imperatively open/close a menu. The design cleanly separates concerns (state, positioning, event handling, rendering) and would tolerate a positioning-algorithm swap without touching component logic.
Tech Stack
A React 16.14+ / React 18+ peer-dependency library written in plain JavaScript (not TypeScript), with hand-authored .d.ts type definitions maintained separately under types/. The only runtime dependency is react-transition-state (the author’s own companion package) for transition state management. The build pipeline uses Rollup (rollup.config.mjs) to produce dual CJS/ESM output (dist/cjs, dist/esm), Babel for JSX/preset-env transpilation, and Sass for compiling the optional CSS theme (src/styles) shipped separately from the JS bundle so consumers can opt in to default styling. TypeScript is used only to type-check the hand-written declaration files (types/tsconfig.json), not to compile the source.
Code Quality
The project has a substantial Jest test suite (src/__tests__/) covering Menu, ControlledMenu, SubMenu, MenuItem, MenuGroup, useMenuState, styling, and a dedicated SSR test file, using @testing-library/react and jest-axe for accessibility assertions rather than shallow rendering. ESLint (flat config) is wired with eslint-plugin-react-hooks and a hooks-addons plugin plus Prettier for formatting, and lint/pret checks run as part of the build script. Error handling is minimal but intentional (e.g. Menu throws explicitly if menuButton is missing rather than failing silently). Naming is consistent and the codebase avoids any-typed escape hatches in its declaration files.
What Makes It Unique
Most dropdown/menu libraries in the React ecosystem either come bundled inside a full component-library (pulling in unrelated primitives) or leave WAI-ARIA menu semantics and keyboard navigation to the consumer. This library’s narrow focus — just menus, unstyled, with the ARIA authoring-practices pattern implemented directly and a genuine SSR story via initialMounted — is the differentiator, not a novel technical mechanism. The react-transition-state-driven four-state lifecycle (rather than a simple open/closed boolean) is a deliberate, if not groundbreaking, choice that gives consumers hooks for enter/exit animations without extra state tracking of their own.