UX Standards — 18: Table Cell Right-Click Context Menu
Governs: The context menu that appears when right-clicking on any table cell.
Parent rules: See 07-UNIVERSAL-DATA-TABLE.md and 00-OVERVIEW-AND-CSS-RULES.md first.
Overview
Right-clicking any data cell in a UDT table opens a context menu with clipboard copy operations. This provides power-user shortcuts for copying data without using the toolbar or row actions.
The context menu does NOT appear on: the checkbox column, the actions column (...), or table headers. It applies to data cells only.
Visual Anatomy
(right-click on a data cell)
┌───────────────────────────────┐
│ 📋 Copy Selection (10 cells) │ ← Only shown when multi-cell selected
│ 📋 Copy Cell │
│ 📋 Copy Cell with Header │
│ 📋 Copy Row │
│ 📋 Copy Column │
└───────────────────────────────┘
Menu Items
| Item | Icon | Copies | Shown When |
|---|---|---|---|
| Copy Selection (N cells) | Copy | All selected cells as TSV | 2+ cells selected |
| Copy Cell | Copy | The single cell's value as plain text | Always |
| Copy Cell with Header | Copy | Column Name: Cell Value format | Always |
| Copy Row | Copy | All visible column values of this row as TSV | Always |
| Copy Column | Copy | All values in this column (all rows) as newline-separated | Always |
Component Implementation
// Using @radix-ui/react-context-menu (included in shadcn/ui):
<ContextMenu>
<ContextMenuTrigger asChild>
<td
className="..."
onContextMenu={(e) => handleCellContextMenu(e, row, column)}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
</ContextMenuTrigger>
<ContextMenuContent className="w-[220px]">
{/* Copy Selection — only if multi-cell selection active */}
{selectedCells.length > 1 && (
<ContextMenuItem onClick={() => copySelection(selectedCells)}>
<Copy className="mr-2 h-4 w-4" />
Copy Selection ({selectedCells.length} cells)
</ContextMenuItem>
)}
<ContextMenuItem onClick={() => copyCell(cell)}>
<Copy className="mr-2 h-4 w-4" />
Copy Cell
</ContextMenuItem>
<ContextMenuItem onClick={() => copyCellWithHeader(cell, column)}>
<Copy className="mr-2 h-4 w-4" />
Copy Cell with Header
</ContextMenuItem>
<ContextMenuItem onClick={() => copyRow(row)}>
<Copy className="mr-2 h-4 w-4" />
Copy Row
</ContextMenuItem>
<ContextMenuItem onClick={() => copyColumn(column, table)}>
<Copy className="mr-2 h-4 w-4" />
Copy Column
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
Copy Behavior Details
Copy Cell
Copies: "superadmin@anshinhealth.com"
Format: Raw cell display value as plain text string
Copy Cell with Header
Copies: "Email: superadmin@anshinhealth.com"
Format: "{column header}: {cell value}"
Copy Row
Copies all visible columns separated by tab character:
"superadmin\tSuper Administrator\t\tsuperadmin@anshinhealth.com\tActive\t—\t—\tSuper Staff..."
Format: TSV (Tab-Separated Values) — pastes cleanly into Excel/Sheets
Copy Selection (N cells)
Copies the rectangular cell selection as TSV:
"superadmin@anshinhealth.com\tActive
system@anshinhealth.internal\tActive"
Format: TSV with newlines between rows
N = total count of selected cells shown in parentheses
Copy Column
Copies all values in the column, one per line:
"superadmin@anshinhealth.com
system@anshinhealth.internal"
Format: Newline-separated values (all rows, not just current page)
Context Menu Spec
| Property | Value |
|---|---|
| Component | ContextMenu + ContextMenuContent (Radix UI / shadcn) |
| Trigger | Right-click (onContextMenu) on any data cell |
| Width | w-[220px] |
| Icon | Copy h-4 w-4 mr-2 on all items |
| Position | Opens at cursor position |
| Close | Any click outside, or after selecting an item |
Clipboard API
// All copy operations use the Clipboard API:
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text);
toast.success('Copied to clipboard');
} catch {
// Fallback for older browsers:
const el = document.createElement('textarea');
el.value = text;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
toast.success('Copied to clipboard');
}
};
Success feedback: toast.success('Copied to clipboard') shown after each copy action. No error state shown to user — if clipboard access fails silently, the operation is skipped.
Multi-Cell Selection
When the user holds Shift or Ctrl/⌘ and clicks cells to create a multi-cell selection:
- Selected cells highlight with
bg-primary/10background - The selection count is tracked in
selectedCellsstate - Right-clicking during a selection shows "Copy Selection (N cells)" as the first item
Multi-cell selection is an optional enhancement. If the table does not implement cell-level selection, only the single-cell items (Copy Cell, Copy Cell with Header, Copy Row, Copy Column) are shown.
Cells Where Context Menu Does NOT Appear
| Column | Reason |
|---|---|
Checkbox column (select) | Clicking is reserved for row selection |
Actions column (...) | Right-click opens the row actions DropdownMenu instead |
Table headers (<th>) | Headers have their own filter popup (see 09-COLUMN-FILTER-POPUP.md) |
Violation Checklist
- Uses
ContextMenu/ContextMenuContentfrom shadcn/ui (Radix) — NOT a customdiv - Context menu width:
w-[220px] - All items use
Copy h-4 w-4 mr-2icon - "Copy Selection (N cells)" only appears when 2+ cells are selected
- N in "Copy Selection (N cells)" reflects the actual selected cell count
- Copy Row produces tab-separated values (TSV)
- Copy Selection produces TSV with newlines between rows
- All copy operations use
navigator.clipboard.writeText() -
toast.success('Copied to clipboard')shown after each operation - Context menu does NOT appear on checkbox or actions columns