RightClickMenu

Opens a menu of actions where the user right-clicks.

Installation

  1. Install the package.
npm install @hashintel/ds-components
  1. Import the component.
import { RightClickMenu } from "@hashintel/ds-components";

export const EntityCard = ({ entityId }: { entityId: string }) => (
  <RightClickMenu
    items={[
      { text: "Open entity", onClick: () => openEntity(entityId) },
      { text: "Duplicate", onClick: () => duplicateEntity(entityId) },
    ]}
  >
    <article>{/* the right-clickable area */}</article>
  </RightClickMenu>
);

When to use

  • Do: Use RightClickMenu as a shortcut to actions on a specific object — a table row, graph node, file, or canvas region.
  • Do: Use it on large surfaces (canvases, editors) where a persistent toolbar for every object would add noise.

When not to use

  • Don't: Avoid making it the only path to an action — right-click is invisible until tried, and many users never try.
  • Don't: Don't wrap text inputs or selectable prose — it suppresses the browser's native context menu (copy, paste, spell-check) and disables text selection in the area.

Anatomy

The wrapped children are the right-clickable area; items describes the menu rows using the same items model as Menu — actions, links, submenus, and labelled groups:

<RightClickMenu
  items={[
    // an action — onClick receives the item's id (defaults to its text)
    { text: "Open entity", onClick: openEntity },
    // a link
    { text: "Open in new tab", href: entityUrl, target: "_blank" },
    // a group draws dividers; an empty label makes it a plain divider
    {
      id: "danger",
      label: "",
      items: [{ text: "Delete entity", tone: "error", onClick: confirmDelete }],
    },
  ]}
>
  <EntityCard /> {/* the right-clickable area */}
</RightClickMenu>

Examples

Basic

Pass items with an onClick per action and wrap the area the menu belongs to. Right-click (or long-press on touch) anywhere inside it opens the menu at the pointer.

Grouped actions

Wrap related items in { id, label, items } groups — dividers draw between groups automatically. Pass an empty label for a divider without a heading.

Icons and shortcut hints

An item's icon reinforces its label; suffix right-aligns extra content, conventionally the keyboard shortcut. The suffix only displays the hint — the shortcut itself must be registered elsewhere.

Disabled items

Disable actions that don't currently apply instead of removing them — a stable list keeps the menu predictable between opens. Disabled items are skipped by keyboard navigation.

Destructive actions

Set tone: "error" on destructive items and keep them last, in a group of their own, so a slipped pointer doesn't land on them. Confirm irreversible actions before running them.

Give an item subItems to open a nested menu on hover or ArrowRight. Keep nesting to one level — deeper trees are hard to traverse with a pointer.

Checkable view options

Mark stateful rows with selected and selectedStyle: "checkbox"; keepOpenOnSelect keeps the menu up while the user toggles several. Rebuild items from state — the menu renders them fresh on every open.

import { useState } from "react";

import { RightClickMenu } from "@hashintel/ds-components";

export const GraphCanvas = () => {
  const [showGrid, setShowGrid] = useState(true);
  const [snapToGrid, setSnapToGrid] = useState(false);
  return (
    <RightClickMenu
      items={[
        {
          text: "Show grid",
          selected: showGrid,
          selectedStyle: "checkbox",
          keepOpenOnSelect: true,
          onClick: () => setShowGrid((previous) => !previous),
        },
        {
          text: "Snap to grid",
          selected: snapToGrid,
          selectedStyle: "checkbox",
          keepOpenOnSelect: true,
          onClick: () => setSnapToGrid((previous) => !previous),
        },
      ]}
    >
      <section>{/* canvas */}</section>
    </RightClickMenu>
  );
};

Items with href render real anchors — right for "Open in new tab" rows. target: "_blank" is the only target supported.

Placement

The menu opens at the pointer; position sets the preferred side and alignment relative to it, and another placement is chosen when the preferred one doesn't fit.

Behavior

  • The menu opens on right-click, or a long press from touch and pen pointers. Opening suppresses the browser's native context menu inside the area.
  • Menu contents mount on open and unmount on close, so items built from current state are always fresh.

Content

  • Write item labels verb-first, 1–3 words, sentence case: "Rename", "Copy entity ID", "Delete entity". No punctuation.
  • Show only actions relevant to the object, most-used first — a context menu is a shortcut, not a command reference. Keep it under ~10 items per level, grouping once you pass 5.
  • Keep group labels to one or two category words: "View", "Organize".

Accessibility

RightClickMenu implements the WAI-ARIA menu pattern.

What RightClickMenu provides

  • Menu semantics, highlight-based arrow-key navigation, typeahead, and Escape dismissal.
  • Long-press opening on touch and pen pointers, so the menu isn't mouse-only.

What you must do

  • Make the wrapped area focusable (or ensure it contains a focusable element) so keyboard users can reach it and open the menu.

API reference

RightClickMenu

PropTypeDescription
items
ItemOrGroup<MenuItem>[]
children
ReactNode
position
Position
default: "bottom-start"

The shape of items and subItems entries — the same items model as Menu. Each takes text plus exactly one of onClick, href, or subItems — or a lone custom node for a presentational row.

PropTypeDescription
description
ReactNode
icon
"function" |⁠ "filter" |⁠ "at" |⁠ "circle" |⁠ … 137 more
loading
boolean
suffix
ReactNode
indent
number
disabled
boolean
tone
"neutral" |⁠ "brand" |⁠ "error"
selectedStyle
"checkbox" |⁠ "tick" |⁠ "highlight"
default: "highlight"
selectedTone
"neutral" |⁠ "brand" |⁠ "error"
keepOpenOnSelect
boolean
id
string
text
ReactNode
href
string
target
"_blank"
onClick
((id: string) => void)
subItems
ItemOrGroup<Item>[]
menuClassName
string
custom
ReactNode
selected
boolean

A labelled group of items, usable wherever items are: { id, label, items }.

PropTypeDescription
id
string
label
ReactNode
items
MenuItem[]

Was this page helpful?

On this page