Skip to main content

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 │
└───────────────────────────────┘

ItemIconCopiesShown When
Copy Selection (N cells)CopyAll selected cells as TSV2+ cells selected
Copy CellCopyThe single cell's value as plain textAlways
Copy Cell with HeaderCopyColumn Name: Cell Value formatAlways
Copy RowCopyAll visible column values of this row as TSVAlways
Copy ColumnCopyAll values in this column (all rows) as newline-separatedAlways

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

PropertyValue
ComponentContextMenu + ContextMenuContent (Radix UI / shadcn)
TriggerRight-click (onContextMenu) on any data cell
Widthw-[220px]
IconCopy h-4 w-4 mr-2 on all items
PositionOpens at cursor position
CloseAny 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/10 background
  • The selection count is tracked in selectedCells state
  • 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

ColumnReason
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 / ContextMenuContent from shadcn/ui (Radix) — NOT a custom div
  • Context menu width: w-[220px]
  • All items use Copy h-4 w-4 mr-2 icon
  • "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