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.162.0

Published

## Overview

Readme

Context Menu

Overview

ContextMenu is an anchored panel of selectable cells. The component is built around two primitives: a single ContextMenuItem whose type prop switches between cell variants (option, search, heading, divider), and a panel that supports a preset shorthand (type="list" | "phone" | "checkbox" | "radio" | "status" | "brandLogo" | "avatar" | "loading") plus a fully custom composition path via children.

When to use

  • A primary action menu attached to a trigger (e.g. a button's overflow actions).
  • A selection control with checkbox or radio cells (multi-select or single-select).
  • A lightweight picker for status, country, or brand-logo lists.

When not to use

  • For form fields driven by validation — use Select, Autocomplete or a plain Input instead.
  • For navigational menus or app-level navigation — use the appropriate navigation primitives.
  • For a single destructive confirmation — use a Dialog.

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. | | 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 |