npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xsolla/xui-context-menu

v0.187.0

Published

<!-- BEGIN:xui-mcp-instructions:context-menu --> A floating panel containing a vertical list of ContextMenuCell rows. Serves as the shared dropdown layer for multiple input components — Select, Multiselect, Combobox, StatusDropdown, Phone input — as well

Readme

Context Menu

A floating panel containing a vertical list of ContextMenuCell rows. Serves as the shared dropdown layer for multiple input components — Select, Multiselect, Combobox, StatusDropdown, Phone input — as well as for contextual action menus triggered by right-click or an overflow button. Each instance is a preset configuration of cells — the Type prop selects a ready-made composition suited to a specific use case. All cells within one menu share the same Size.

When to use

  • As the dropdown panel for Select — single-value picker from a predefined list
  • As the dropdown panel for Multiselect — multiple-value picker; use Type=Checkbox so selections stay visible
  • As the dropdown panel for Autocomplete — use Type=Search + options
  • As the dropdown panel for StatusDropdown — inline status picker; use Type=Status
  • As the dropdown panel for Phone input — country dial-code picker; use Type=Phone
  • To surface contextual actions for a selected item or element (right-click menu, overflow "⋯" button)
  • When the number of actions or options is too large for inline buttons but too small for a full page

When not to use

  • As the primary navigation of a page and top-level site navigation
  • When there are only 1–2 actions — use ToggleButtonGroup, Radio or Checkboxes
  • When the selection requires multiple steps or complex input — use a Dialog or a dedicated panel

Content guidelines

  • Option labels — concise imperative verbs for actions ("Delete", "Rename", "Export"); nouns for selections ("English", "Admin", "Dark mode"). Keep to 1–4 words.
  • Heading labels — short noun phrases in title case or uppercase; describe the group below. Examples: "Actions", "Sort by", "FILTERS".
  • Destructive label colour — use the Alert/red colour token for the label text of destructive options. Do not use red for informational or reversible actions.
  • Search placeholder — use "Search…" or a more specific variant: "Search countries…", "Search members…".
  • Empty state message — be specific: "No countries match", "No team members found". Avoid generic "No results" when a more informative string is available.
  • Menu length — aim for 7 or fewer options before considering grouping, search, or a different pattern. More than 12 options without search significantly degrades usability.

Behaviour guidelines

  • Trigger — the menu opens from a trigger element: right-click on a target, click on an overflow button (⋯), or programmatic call. The trigger element should have aria-haspopup="menu" and aria-expanded updated on open/close.
  • Positioning — the panel is positioned relative to the trigger. Default placement is bottom-start; fallback to top-start, bottom-end, or top-end when the default position would clip the viewport. Never let the panel appear partially off-screen.
  • Width — the panel width is set by Size × Type. Do not stretch it to fill the viewport or a parent container. If option labels are longer than the panel width, truncate with ellipsis and show a tooltip on hover.
  • Single-select close — for Type=List, Phone, Avatar, BrandLogo, Status, and Radio, the menu closes automatically after the user selects an option. The trigger updates to reflect the new value.
  • Multi-select stay open — for Type=Checkbox, the menu stays open after each selection. Close on: clicking outside, pressing Escape, or an explicit "Apply" / close action.
  • Loading state — show Type=Loading immediately when the menu opens and the option list has not yet been fetched. The panel renders at its normal dimensions with a centered Loader spinner replacing all cell content. Switch to the real Type as soon as data arrives. Do not show an empty panel while loading.
  • Empty state — if search filtering produces no results, show an empty state message inside the panel (e.g. "No results found") rather than closing the menu or showing a blank panel.
  • Search filtering — when a Type=Search cell is present, typing filters visible options in real time with ~200ms debounce. Heading cells and dividers that become empty after filtering should be hidden alongside their options to avoid orphaned labels.
  • Sub-menus — an option with a chevron in its right slot opens a nested ContextMenu on hover (desktop) or tap (touch). The parent item remains in Hover/Active state while the sub-menu is open. ← / Escape closes the sub-menu and returns focus to the parent item.
  • Destructive actions — place destructive options (delete, remove, revoke) at the bottom of the list, separated from safe actions by a Type=Divider cell. Render in the Alert/red colour token.
  • Scroll — if the option list exceeds the viewport height, the panel body becomes scrollable. Pin the Type=Search cell to the top so it remains visible while the user scrolls through options. Pin a footer action row to the bottom if one is present.
  • Focus management — when the menu opens, focus moves to the first interactive option (or to the Search input if present). When the menu closes, focus returns to the trigger element.

Accessibility

  • The panel must have role="menu" (for action menus) or role="listbox" (for selection menus). Each Type=Option cell must have role="menuitem", role="menuitemcheckbox", or role="option" accordingly.
  • The trigger element must have aria-haspopup="menu" and aria-expanded="true" / "false" to reflect the open state.
  • Type=Heading cells must either wrap their option group in role="group" with aria-labelledby pointing to the heading, or carry role="presentation" themselves.
  • Type=Divider cells must have role="separator".
  • Type=Search input must have role="searchbox" and aria-label (e.g. aria-label="Search options").
  • When the menu opens, move focus to the first focusable option (or the Search input if present).
  • Keyboard navigation: ↑ / ↓ move between options (skipping Heading and Divider); Enter / Space activate the focused option; Escape closes the menu and returns focus to the trigger; Tab closes the menu and moves focus to the next element in the page.
  • For sub-menus: → / Enter opens the sub-menu; ← / Escape closes it and returns focus to the parent item.
  • When the menu closes, focus must return to the trigger element that opened it.
  • For Type=Checkbox (multi-select), each option must have aria-checked="true" / "false". For Type=Radio and single-select list types, use aria-selected or aria-checked consistently with the chosen ARIA role.

Installation

yarn add @xsolla/xui-context-menu

Two API paths

Preset path

Pass type and items. The panel renders the preset's chrome and composes each option with the right control or slot.

import { ContextMenu } from "@xsolla/xui-context-menu";

<ContextMenu
  type="list"
  trigger={<Button>Open</Button>}
  items={[
    { type: "option", label: "Edit" },
    { type: "option", label: "Duplicate" },
    { type: "option", label: "Delete", destructive: true },
  ]}
/>;

Custom path

Compose cells as children when you need full control of the layout, want to mix headings and dividers freely, or render a slot the preset path doesn't cover.

import { ContextMenu, ContextMenuItem } from "@xsolla/xui-context-menu";

<ContextMenu trigger={<Button>Open</Button>} aria-label="Actions">
  <ContextMenuItem type="heading" label="Workspace" />
  <ContextMenuItem type="option" label="Personal" />
  <ContextMenuItem type="option" label="Acme Inc." />
  <ContextMenuItem type="divider" />
  <ContextMenuItem type="option" label="Sign out" destructive />
</ContextMenu>;

Choose the preset path for typical menus where the data is uniform; choose the custom path when cells differ structurally or when you need to drop in bespoke nodes between cells.

ContextMenuItem reference

ContextMenuItem is a discriminated union on type. All cell types accept size, data-testid and theme-override props.

type="option"

| Prop | Type | Purpose | | --- | --- | --- | | label | ReactNode | Primary cell text (required). | | description | ReactNode | Secondary line beneath the label. | | leadingControl | "checkbox" \| "radio" | Renders a Checkbox or Radio at the start. | | leadingIcon | ReactNode | Icon node before the label group. | | status | ReactNode | Status indicator slot (e.g. <Status>). | | iconWrapper | ReactNode | Wrapped icon / avatar slot. | | slot / slotContent | ReactNode | Generic slot before the label. | | value | ReactNode | Right-side primary text (e.g. shortcut value, dial code). | | hint | ReactNode | Right-side secondary text below value. | | trailingIcon | ReactNode | Trailing icon at the end of the cell. | | keyboardShortcut | string | Display-only shortcut rendered as <kbd> and exposed via aria-keyshortcuts. | | hasSubmenu | boolean | Marks the cell as a submenu trigger and renders a chevron. | | submenu | ReactNode | A nested <ContextMenu> opened on hover/ArrowRight/Enter. | | checked | boolean | Fully controlled checked state. | | disabled | boolean | Disables interaction and applies the disabled style. | | destructive | boolean | Applies the destructive content colour. | | onSelect | () => void | Fires on activation (click, Enter, Space). | | onCheckedChange | (checked: boolean) => void | Optional change callback for controls. |

Render order (left → right): leadingControl, leadingIcon, status, iconWrapper, slotContent, label (with optional description below), value (with optional hint below), keyboardShortcut, submenu chevron, trailingIcon.

type="search"

| Prop | Type | Purpose | | --- | --- | --- | | value | string | Controlled value (required). | | onValueChange | (value: string) => void | Change callback (required). | | placeholder | string | Defaults to "Search". | | autoFocus | boolean | Focuses the input on mount. | | aria-label | string | Defaults to "Search options". |

type="heading"

| Prop | Type | Purpose | | --- | --- | --- | | label | ReactNode | Section title (uppercase styling). | | description | ReactNode | Optional helper line beneath. |

type="divider"

A horizontal rule with role="separator". No content props.

ContextMenu reference

| Prop | Type | Purpose | | --- | --- | --- | | type | "list" \| "loading" \| "phone" \| "checkbox" \| "status" \| "brandLogo" \| "radio" \| "avatar" | Panel preset; works with items. | | items | ReadonlyArray<Option \| Heading \| Divider> | Data-driven cells for the preset path. | | children | ReactNode | Custom-composition cells (alternative to items). | | size | "sm" \| "md" \| "lg" \| "xl" | Controls cell sizing across the panel. Default md. | | searchable | boolean | Auto-renders a sticky search cell and filters options. | | loading | boolean | Renders a centred spinner instead of the cell list. | | emptyMessage | string | Custom message for the default empty state. | | empty | ReactNode | Replace the empty state entirely. | | trigger | ReactNode | Element that toggles the panel; receives aria-haspopup / aria-expanded. | | placement | "bottom-start" \| "top-start" \| "bottom-end" \| "top-end" | Initial placement. Auto-flips when clipped. | | isOpen | boolean | Controlled open state. | | onOpenChange | (open: boolean) => void | Open-state callback. | | closeOnSelect | boolean | Override the per-preset default. | | width | number | Forces panel width (px). | | maxHeight | number | Caps panel height (px); body scrolls and search stays sticky. | | onSelect | (item: ContextMenuOptionItemProps) => void | Fires for the preset path on option activation. | | aria-label | string | Accessible name for the menu container. | | testID | string | Testing handle; forwarded as data-testid when data-testid is not provided. | | data-testid | string | Testing handle. |

Behaviour & accessibility

  • The panel root is role="menu" (or hosts role="menuitemcheckbox" / role="menuitemradio" cells when checked is provided). Headings render as role="presentation", dividers as role="separator", and the search cell as role="searchbox".
  • closeOnSelect defaults to true for every preset except checkbox, where multi-select keeps the panel open.
  • On open, focus moves to the search input when present, otherwise to the first option.
  • On close, focus returns to the trigger.
  • The trigger element receives aria-haspopup="menu" and aria-expanded synced to the open state.

Keyboard reference

| Key | Action | | --- | --- | | / | Move active option up/down (skips heading/divider). | | Enter / Space | Activate the focused option. | | Esc | Close the menu and return focus to the trigger. | | Tab | Close the menu and continue natural focus order. | | Home / End | Jump to the first/last option. | | / Enter | Open a submenu when on a hasSubmenu option. | | / Esc | Close the submenu and return focus to its parent option. |

Content guidelines

  • Use short, imperative labels ("Edit", "Duplicate", "Sign out").
  • Use sentence case for option labels.
  • Use uppercase for headings — the heading cell already applies the visual treatment.
  • Place destructive options at the bottom of the list, ideally separated by a divider.
  • Prefer specific empty messages ("No countries match") over the generic default.
  • Aim for seven options or fewer per panel; group with headings or split into submenus when longer.

Migration from prior API

| Old | New | | --- | --- | | ContextMenuCheckboxItem | <ContextMenuItem type="option" leadingControl="checkbox" /> | | ContextMenuRadioItem | <ContextMenuItem type="option" leadingControl="radio" /> | | ContextMenuRadioGroup | type="radio" panel preset with shared selected state | | ContextMenuGroup | <ContextMenuItem type="heading" /> | | ContextMenuSeparator | <ContextMenuItem type="divider" /> | | ContextMenuSearch | <ContextMenuItem type="search" /> (or set searchable: true on the panel for auto-render) | | Size scale s / m / l / xl | sm / md / lg / xl |