Support (or document) `trigger="contextMenu"` on collection items (GridListItem / TreeItem / Row)
- Dominant language
- TypeScript
- Stars
- 15.9k
- Forks
- 1.6k
- Avg merge
- 3d 9m
- Merged PRs (30d)
- 59
Description
### Provide a general summary of the feature here
`trigger="contextMenu"` (new in v1.20.0) works well for a standalone trigger, but there doesn't appear to be a supported way to use it on a **collection item** — a `GridListItem`, `TreeItem`, or table `Row`. Right-clicking a row to open the same menu its kebab button opens seems like one of the most common reasons to want a context menu, so I suspect this is either a gap or a pattern I've failed to find.
Either a supported API for it, or docs guidance on the intended approach, would help.
### 🤔 Expected Behavior?
Being able to attach a context menu to a collection item, so that right-click / long-press / Shift+F10 on a row opens the same menu the row's kebab button opens.
Collection items already look like valid trigger candidates: the docs say `MenuTrigger` "works with any pressable React Aria component", and rows are pressable —
- `useGridListItem` → `useSelectableItem` → `usePress` (`useSelectableItem.ts:409`)
- `usePress` reads `PressResponderContext` (`usePress.ts:112`) and merges the remaining DOM props into its result (`usePress.ts:1010`)
so a row would consume `MenuTrigger`'s `onContextMenu` / `onKeyDown` correctly if it could receive them.
### 😯 Current Behavior
**1. `MenuTrigger` can't wrap a collection item.** `MenuTrigger` must render as an ancestor of its trigger, but inside a collection it hits the `useIsHidden` guard and returns `null` during the collection-building pass, so the item never registers. Wrapping a `TreeItem` removes every row from the tree — the collection renders empty.
**2. Wrapping the row's *content* in `` works, but warns once per row.** The `Pressable` child ends up being a `div` with no interactive ARIA role. The row itself is `role="row"` (RAC's Tree is a treegrid), which isn't in `Pressable`'s allowed-role list, so there's nothing valid to put the role on:
```
child must have an interactive ARIA role.
```
Adding `excludeFromTabOrder` just swaps it for the other warning:
```
child must be focusable. Please ensure the tabIndex prop is passed through.
```
Putting a fake interactive role on a `div` inside a `treegrid` row seems worse than the warning, so neither option feels right.
**3. Every example uses a standalone ``.** `packages/react-aria-components/stories/Menu.stories.tsx:532`, `packages/@react-spectrum/s2/stories/Menu.stories.tsx:409`, and `packages/react-aria-components/test/Menu.test.tsx:652` / `:701`, plus both docs sites. As a side note, the docs example makes a non-interactive 250×150 region into a `` purely to satisfy the pressable requirement — it's announced as a button but does nothing when pressed, which reads a little odd as the canonical example.
### 💁 Possible Solution
`useContextMenu` works today and looks like the intended escape hatch — `contextMenuProps` spread straight onto a `TreeItem` / `GridListItem` keeps all the platform handling, with no wrapper and no warnings. If that's the recommended pattern for collections, documenting it on the Menu page (or on GridList / Tree / Table) would settle it.
The one thing it loses versus `MenuTrigger` is positioning the menu at the pointer, which needs `getTargetRect` — that isn't part of `Popover`'s public types, so it currently needs a `@ts-expect-error`. Exposing it, or otherwise providing a supported way to anchor a popover to a point, would close that gap.
### 🔦 Context
We have a collections sidebar built on `Tree`, where each row has a kebab menu (rename, share, delete, …). We wanted right-click on the row to open that same menu, keeping the kebab button as-is. The kebab stays either way — the context menu is an addition, not a replacement.
We landed on `` around the row content, which works, but it restructures the row purely to host the trigger and logs a warning per row in development, so it doesn't feel like the intended shape.
### 💻 Examples
Wrapping the item — collection renders empty:
```tsx
// Every row disappears: MenuTrigger returns null during the collection build,
// so no TreeItem is ever registered.
Design
Rename…
Delete…
```
Wrapping the content — works, but warns once per row:
```tsx
{/* warns: child must have an interactive ARIA role */}
Rename…
Delete…
```
Possibly related: #7197 (pass `onContextMenu` in RAC `Tab`), #7987 (`useLongPress` with a Table Row — event and labelling props get filtered out), #9863 (right-click a table row containing a link).
Versions: `react-aria-components@1.20.0`, `react-aria@3.51.0`, `react-stately@3.49.0`, React 19.
### 🧢 Your Company/Team
Metaview
Contributor guide
Research direction
Start with useContextMenu and MenuTrigger, then compare collection-item event handling in useSelectableItem.ts:409 and usePress.ts:112/1010. Inspect the Menu stories and tests named in the issue to determine whether the supported outcome is documentation or an API change. Done means a collection row has a supported, warning-free context-menu path with documented behavior and coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- accessibility, frontend
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100