Skip to content

Long-press pop menu

In review
Live · web

Press & hold a target to pop its actions

Hold ~0.5s (watch it dip), right-click, or focus it and press Enter / Menu / Shift+F10 (or its ⋯). In the menu: move, Enter runs, Esc closes.

Chat · nockerl-design

NockerlMenu opened 0 times · selected: none · pointer, right-click, and keyboard all work; the island is live.

The pop is the same contextual action menu whichever gesture opens it. One menu, several triggers:

Gesture Where Opens
Long-press (press-and-hold) Android (primary), touch web the pop menu
Hover → the ⋯ affordance, click desktop / mouse web the same pop menu (the ⋯ is the DESKTOP entry point)
Right-click / Ctrl-click web, macOS (.contextMenu) the same pop menu
Keyboard (focus ⋯, or Shift+F10 → Enter) web a11y the same pop menu

The hover is therefore not an odd extra affordance; it is simply how a mouse / keyboard user reaches the long-press menu (long-press is a touch gesture, so the desktop needs a visible entry point to the same menu).

Tap-to-select is a different gesture. Tapping a chat bubble (its onSelect) selects the item (a toggle + soft cyan wash); it does not open the action menu. Reach for the pop menu when an element needs a menu of actions; reach for tap-to-select when it needs selection. The two compose: a bubble can be both selectable and long-press-poppable.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform. Spec + live demo,
// not yet exported from @dizyx/nockerl-react; promotion is tracked.)
export function MessageBubble({ message }: { message: Message }) {
return (
<LongPressPop
actions={[
{ id: 'copy', label: 'Copy', icon: <CopyIcon />, onSelect: => copy(message) },
{ id: 'select', label: 'Select', icon: <CheckIcon />, onSelect: => select(message) },
{ id: 'share', label: 'Share', icon: <ShareIcon />, onSelect: => share(message) },
{ id: 'delete', label: 'Delete', icon: <TrashIcon />, tone: 'danger', onSelect: => remove(message) },
]}
>
<ChatBubble role="agent">{message.text}</ChatBubble>
</LongPressPop>
);
}
PropTypeDefaultDescription
children *ReactNodeThe content element the pop is triggered on. It keeps its own surface and lifts above the scrim when popped.
actions *PopAction[]Action rows: { id, label, icon, tone?, onSelect }. A tone: 'danger' row renders in the error token and is divider-separated at the end.
holdMsnumber500Press-hold threshold before the menu pops. A live scale dip tracks the timer.
disabledbooleanfalseWhen set, no gesture is detected and no feedback plays (the target stays interactive for its own tap).
onOpenChange(open: boolean) => voidNotified when the menu opens / closes (e.g. to pause a list, fire a haptic).