# Context menu

> Long-press an element and it lifts out of the page above a dimmed backdrop while a menu springs out beside it; keep the finger down, slide onto an item and release to choose. Right-click opens it on the web.

- Type: Component (overlays)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/context-menu
- Markdown: https://goodui.dev/native/components/context-menu.md
- Registry item: https://goodui.dev/native/r/context-menu.json
- Dependencies: react-native-gesture-handler, react-native-reanimated
- Keywords: react native context menu, long press menu, ios context menu expo, peek menu, message actions menu, right click menu react native web

## When to use

- Secondary actions on content people browse: messages, photos, files, cards.
- Actions that are also reachable elsewhere, so the menu is a shortcut and not the only path.

## When not to use

- The main action of an element: put it in onPress.
- Choices not tied to a visible element: use action-sheet.
- Rows in a list where swiping is the expected shortcut: use swipe-actions.
- Explaining something: use popover.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/context-menu.json
npx expo install --fix
```

The component lands in `components/goodui-native/context-menu/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

### Setup

- Wrap the app in GestureHandlerRootView and ThemeProvider (the menu renders in its overlay layer).

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { ContextMenu } from "@/components/goodui-native/context-menu/context-menu";

<ContextMenu
  accessibilityLabel={`${message.author}: ${message.text}`}
  previewStyle={{ borderRadius: 18 }}
  groups={[
    [
      { key: "reply", label: "Reply", icon: (p) => <ReplyIcon {...p} />, onPress: () => reply(message) },
      { key: "copy", label: "Copy", icon: (p) => <CopyIcon {...p} />, onPress: () => copy(message.text) },
    ],
    [{ key: "delete", label: "Delete", destructive: true, icon: (p) => <TrashIcon {...p} />, onPress: () => remove(message.id) }],
  ]}
>
  <MessageBubble message={message} />
</ContextMenu>
```

## Examples

### Tap to open, hold for more

```tsx
<ContextMenu
  title="Lisbon, October 2026"
  onPress={() => openPhoto(photo.id)}
  actions={[
    { key: "share", label: "Share", icon: (p) => <ShareIcon {...p} />, onPress: share },
    { key: "delete", label: "Delete", destructive: true, onPress: remove },
  ]}
>
  <PhotoTile photo={photo} />
</ContextMenu>
```

### Menu only, no lift

```tsx
<ContextMenu variant="menu" onPress={open} actions={[{ key: "rename", label: "Rename", onPress: rename }]}>
  <FileRow file={file} />
</ContextMenu>
```

## API reference

### ContextMenu

`import { ContextMenu } from "@/components/goodui-native/context-menu/context-menu";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` | – | The element that lifts. Keep it non-interactive and put its tap in `onPress`. |
| `actions` | `ContextMenuAction[]` | – | One group of actions. Use `groups` for several. |
| `groups` | `ContextMenuAction[][]` | – | Several groups separated by a thick divider. |
| `title` | `string` | – | Small heading at the top of the menu. |
| `onPress` | `() => void` | – | A normal tap on the element. |
| `onOpenChange` | `(open: boolean) => void` | – | Called when the menu opens or closes. |
| `variant` | `"lift" \| "menu"` | `"lift"` | `lift` raises a copy of the element above a dimmed backdrop; `menu` only shows the menu next to it. |
| `delayLongPress` | `number` | `350` | Hold duration in milliseconds before the menu opens. Default 350. |
| `menuWidth` | `number` | `250` | Menu width in points. Default 250. |
| `previewStyle` | `StyleProp<ViewStyle>` | – | Shape of the lifted copy. Match the element's own radius; default `{ borderRadius: radius.lg }`. |
| `haptic` | `boolean` | `true` | Haptics on open and while sliding across items. Default `true`. |
| `disabled` | `boolean` | `false` | Dims the control and ignores presses. |
| `accessibilityLabel` | `string` | – | Screen reader label of the element. |
| `labels` | `Partial<ContextMenuLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- The element is one accessible element with a 'Show actions' accessibility action (longpress) and, when onPress is set, the button role and an activate action.
- The hint says 'Touch and hold for actions'; accessibilityState reports expanded while the menu is open.
- The menu has accessibilityRole menu and is modal; items are menuitems with disabled states. Opening announces the title.
- On the web the element is focusable: the ContextMenu key or Shift+F10 opens the menu, Enter or Space runs onPress, Escape closes. Android back closes.
- On the web the element is always a button with aria-haspopup menu; without onPress, Enter and Space open the menu. An accessibilityLabel must start with the element's visible text, or leave it out and let the text name it.

## Motion

- Hold: the element squeezes to 96 % over the long-press delay, so the lift reads as it popping free.
- Open: a copy of the element springs to 103 % on the bouncy spring above the dimmed backdrop (blurred on the web), and the menu grows from the edge facing the element, under its center, from 40 % scale on the bouncy spring.
- Fit: the menu sits below the element, flips above when there is no room, and when neither side fits the lifted element slides up on the smooth spring to make room.
- Slide to choose: while the finger stays down, the item under it highlights; releasing on an item runs it.
- Close: the menu fades, the copy settles back into place on the smooth spring, then the original reappears.
- Reduced motion: no squeeze, no lift scale or menu growth; the menu and backdrop fade and any shift is instant.

## Haptics

- tap when the menu opens
- select each time the finger slides onto a new item

## Performance

- The press, lift, menu and slide-to-choose hit testing run on the UI thread; the menu mounts only while open.
- The lifted copy renders the children a second time while the menu is open; keep heavy children (video, maps) out of it or pass a light element.

## Notes for AI

- Children should not contain their own buttons: the element's tap goes in onPress and a nested Pressable would take the touch first.
- Set previewStyle's borderRadius to the element's own radius so the lifted copy has the same shape.
- Item onPress runs after the menu has closed. Every action needs a unique key.
- Inside a ScrollView, the hold must start before the scroll: moving the finger before the delay cancels the menu and lets the list scroll.

## Related

- [Action sheet](https://goodui.dev/native/components/action-sheet.md): An iOS-style list of choices that springs up from the bottom edge in grouped cards; it follows the finger to be dragged away, and pulling it up stretches the gap between the cards before they spring back together.
- [Popover](https://goodui.dev/native/components/popover.md): A bubble anchored to an element whose arrow always points at the anchor's center, even when the bubble is pushed in from the screen edge; it flips above or below to fit and unfurls out of the arrow tip.
- [Swipe actions](https://goodui.dev/native/components/swipe-actions.md): Row actions that unfold with the finger; a long swipe lets the edge action take over and run, and destructive actions collapse the row before it leaves.

## Guidance for AI tools

Context menu: Long-press an element and it lifts out of the page above a dimmed backdrop while a menu springs out beside it; keep the finger down, slide onto an item and release to choose. Right-click opens it on the web. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
