ContextMenu
The actions that belong to a piece of content — a message, a card, a row — reached by holding the content itself.
Installation
ContextMenu ships with the library — install it once, then import:
import { ContextMenu } from "@delacour/react-native-ui/context-menu";Or copy the source into your project, to own and edit it:
bunx delacour@latest add context-menuUsage
import { ContextMenu } from "@/components/ui/context-menu";
import { Text } from "@/components/ui/text";
import { IconShareOs, IconTrashCan } from "@/lib/icons/central";
import { View } from "react-native";
export default function Example() {
return (
<View className="flex-1 justify-center p-4">
<ContextMenu>
<ContextMenu.Trigger haptic="medium" onPress={() => {}}>
<View className="rounded-lg border border-border bg-card px-4 py-3">
<Text>Are we still on for Thursday?</Text>
</View>
</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item icon={IconShareOs} onSelect={() => {}}>Share</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item icon={IconTrashCan} variant="destructive" onSelect={() => {}}>
Delete
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>
</View>
);
}Anatomy
<ContextMenu>
<ContextMenu.Trigger haptic="medium" onPress={openThread}>
<Card>…</Card>
</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Preview />
<ContextMenu.Item icon={IconShareOs} onSelect={share}>Share</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item variant="destructive" icon={IconTrashCan} onSelect={remove}>Delete</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>A Menu hangs off a control that exists to be opened. A context menu has no such control, because the target is the content: hold it and its actions appear.
The rows and the panel are Menu's. ContextMenu.Item is Menu.Item with its icon at the
trailing edge and a taller row; Label, Separator, CheckboxItem, RadioGroup, RadioItem,
Sub, SubTrigger, SubContent and Background are Menu's own components. Everything the
Menu page says about rows applies here unchanged.
Hold and tap
Holding the trigger for 350ms opens the menu. A short press runs the trigger's onPress. One
recogniser decides which before either fires, so a hold that opens the menu never also counts as
a tap.
Put onPress on the trigger, not on a pressable inside it. An inner press races the hold instead
of being arbitrated against it.
<ContextMenu.Trigger onPress={openThread} haptic="medium">
<ThreadRow thread={thread} />
</ContextMenu.Trigger>The content is not cloned or altered, and need not be pressable. A scroll that starts on the
trigger still scrolls: the hold gives up once the finger drifts past slop.
Anchor
By default the panel opens at the finger. anchor="target" hangs it off the whole trigger instead,
lined up with its leading edge — wherever on the trigger the hold landed.
<ContextMenu.Trigger anchor="target">
<FileRow file={file} />
</ContextMenu.Trigger>Lifted preview
ContextMenu.Preview, placed among the rows, lifts a copy of the held content over the dimmed
screen, a touch larger and casting a shadow. The panel then opens beside the copy rather than at the
finger, so it never covers it.
<ContextMenu.Content>
<ContextMenu.Preview />
<ContextMenu.Item icon={IconHeart}>Favourite</ContextMenu.Item>
</ContextMenu.Content>The preview renders the trigger's children a second time, so any local state inside them starts
fresh. Pass children to ContextMenu.Preview when the copy should differ.
Hold timing
delay is how long the hold lasts before the menu opens; slop is how far the finger may drift in
the meantime. The delay never goes below 150ms — anything shorter is a tap.
<ContextMenu.Trigger delay={600} slop={8}>…</ContextMenu.Trigger>Haptics
haptic on the trigger plays the moment the hold is accepted. haptic on the root is Menu's,
played when a row is chosen. Both are off by default.
Accessibility
The trigger is a single accessible element: a button when it has onPress, and no role
otherwise. It offers two actions. Activate runs onPress, or opens the menu when there is none;
Show menu opens it against the whole trigger, since a screen reader has no finger position. A
disabled trigger announces itself disabled and does nothing. The panel is Menu's — a menu in a
modal layer, with menuitem rows. A tap on the scrim and Android's back button both close it.
API Reference
ContextMenu
Prop
Type
ContextMenu.Trigger
Extends ViewProps.
Prop
Type
ContextMenu.Content
Menu.Content's props, without anchor, and with different defaults.
Prop
Type
ContextMenu.Item
Menu.Item's props, with iconPlacement defaulting to "trailing".
ContextMenu.Preview
Extends ViewProps.
Prop
Type