gridcn1.2.2

Context menu

The data-grid-context-menu add-on: header dropdown and right-click menus for sort, pin, autosize, hide, and row operations.

data-grid-context-menu installs two menus: a header dropdown (and a right-click header menu) for column controls, and a right-click cell menu carrying the row-operation items. It reads grid state through the store hooks, so it mounts inside DataGridProvider.

pnpm dlx shadcn@latest add @gridcn/data-grid-context-menu

Right-click a cell for cut/copy/paste/clear and row operations; right-click a header (or hover it for the ghost chevron) for sort/pin/hide/autosize.

Name
Email
Role
Score
Silent Tiger
user0@example.com
Viewer
4
Quick Lion
user1@example.com
Viewer
89
Swift Wolf
user2@example.com
Manager
80
Eager Lion
user3@example.com
User
51
Eager Tiger
user4@example.com
Viewer
83
Silent Wolf
user5@example.com
User
15
Swift Lion
user6@example.com
Viewer
62
Eager Lion
user7@example.com
Manager
42
Eager Eagle
user8@example.com
Editor
35
Bold Tiger
user9@example.com
Editor
52

Header surface

Both the header dropdown and the right-click header menu show the same items: Sort asc/desc/clear, Pin left/right/unpin, Autosize, Hide. Pin is set with pin: 'left' | 'right' per column as an initial value and changed at runtime through these menus (the pin behavior itself is documented on Columns).

import {
  DataGridContextMenu,
  DataGridHeaderDropdown,
} from "@/components/data-grid-context-menu/data-grid-context-menu";

<DataGridProvider {...grid} columns={columns}>
  <DataGridContextMenu>
    <DataGridRoot renderHeaderMenu={(ctx) => <DataGridHeaderDropdown {...ctx} />}>
      <DataGridHeader />
      <DataGridBody />
    </DataGridRoot>
  </DataGridContextMenu>
</DataGridProvider>;

DataGridContextMenu must be inside DataGridProvider

It reads grid state through the same selector hooks that your own components use. Mounting it outside DataGridProvider throws immediately.

The header dropdown opens by keyboard too. Shift+Alt+ArrowDown (the openColumnMenu action) opens the menu of the active cell's column. Arrow keys move through the items, and Escape returns focus to the cell.

Cell surface: row-operation items

The row-operation items act on every view row covered by the current selection, falling back to the right-clicked row when nothing is selected, so a range selection duplicates or deletes all of its rows at once. Every mutating item (everything but Copy) is disabled on a readOnly grid. Right-clicking a cell makes it the active cell with a single-cell selection before the menu opens, so the items always act on a known target. If a Cut or Copy write fails, the next time the menu opens it disables both items with the labels.contextMenu.clipboardFailed tooltip. After that menu closes, the items are enabled again.

The cell-surface items, in order:

  1. Cut / Copy / Paste: the native clipboard pipeline (see Clipboard).
  2. Clear contents. Empties the selection to each type's clearValue() (the clearContents action, Delete/Backspace).
  3. Insert row above (Ctrl/Cmd+Shift+U) / Insert row below (Ctrl/Cmd+Shift+F). Anchored at the right-clicked row. Hidden without createRow.
  4. Duplicate row(s): Ctrl/Cmd+Shift+X. Hidden without duplicateRow.
  5. Delete row(s): Ctrl/Cmd+Shift+Backspace, destructive item.

Surfaces with no menu: row markers, the empty grid space below the last row, and the scrollbar gutter right-click to nothing at all: no menu and no browser context menu either, matching the quiet no-op of a plain click on empty space. The full operation contract (when items appear under which props) lives on Row operations.

Custom menu items

The menus are data. Each surface resolves its items through a resolver prop on DataGridContextMenu that receives the built-in set as a GridMenuItemSpec[] array (defaults) and returns the complete item set. Return defaults unchanged, filter it, reorder it, extend it, or build a fully custom set.

PropSurfaceCalled with
getCellMenuItemsthe right-click cell menu(ctx, defaults). ctx is { rowId, viewRowIndex, columnId, canInsertRow, canDuplicateRow }: the right-clicked cell's stable row id, its view row, its column id, and the createRow/duplicateRow availability flags the built-in set gates its Insert/Duplicate items on
getHeaderMenuItemsthe right-click header menu AND every DataGridHeaderDropdown inside the wrapper(ctx, defaults). ctx is { columnId, scrollRoot }: the right-clicked column's id and the grid's scroll-container element (null when the right-clicked element has no grid ancestor)

CellMenuItemsResolver and HeaderMenuItemsResolver are the function types of getCellMenuItems and getHeaderMenuItems.

The built-in entries carry stable ids, so a resolver keeps or drops them by id:

SurfaceBuilt-in ids, in order
Cellcut, copy, paste, sep-clipboard, clear-contents, sep-rows, insert-row-above, insert-row-below, duplicate-rows, delete-rows (the Insert entries appear only with createRow, the Duplicate entry only with duplicateRow)
Headersort-asc, sort-desc, clear-sort, sep-sort, pin-left, pin-right, unpin, hide-column, sep-autosize, autosize, autosize-all (clear-sort only while the column is sorted, unpin only while it is pinned, the autosize items only while resizing is enabled)

GridMenuItemSpec is an item or a separator. An item carries id, label, icon (a React node), shortcut (a keymap binding rendered as keycaps), disabled, destructive, tooltip (hover hint while disabled), and onClick. A separator is { id, separator: true }.

The menus are data: each resolver receives the built-in item set and returns the kept entries by id. Right-click a cell for the two kept row items; the header dropdown and the right-click header menu both keep only Sort ascending.

Name
Email
Score
Silent Tiger
user0@example.com
4
Quick Lion
user1@example.com
89
Swift Wolf
user2@example.com
80
Eager Lion
user3@example.com
51
Eager Tiger
user4@example.com
83
Silent Wolf
user5@example.com
15
Swift Lion
user6@example.com
62
Eager Lion
user7@example.com
42
Eager Eagle
user8@example.com
35
Bold Tiger
user9@example.com
52

Keep two row items for the cell surface, and one sort item for both header surfaces:

import { DataGridContextMenu } from "@/components/data-grid-context-menu/data-grid-context-menu";

<DataGridContextMenu
  getCellMenuItems={(_ctx, defaults) =>
    defaults.filter((s) => s.id === "insert-row-below" || s.id === "duplicate-rows")
  }
  getHeaderMenuItems={(_ctx, defaults) => defaults.filter((s) => s.id === "sort-asc")}
>
  <DataGridRoot>
    <DataGridHeader />
    <DataGridBody />
  </DataGridRoot>
</DataGridContextMenu>

Extend the built-in set with a custom item. The resolver closes over hook values that a component above it already holds:

import { Plus } from "lucide-react";
import { useDataGridActions } from "@/components/data-grid/data-grid";
import { DataGridContextMenu } from "@/components/data-grid-context-menu/data-grid-context-menu";

function GridWithExtraItem() {
  const actions = useDataGridActions();
  return (
    <DataGridContextMenu
      getCellMenuItems={(ctx, defaults) => [
        ...defaults,
        {
          id: "new-row-here",
          label: "New row here",
          icon: <Plus className="size-4 text-muted-foreground" />,
          onClick: () => actions.insertRow(ctx.viewRowIndex, "below"),
        },
      ]}
    >
      <DataGridRoot>
        <DataGridHeader />
        <DataGridBody />
      </DataGridRoot>
    </DataGridContextMenu>
  );
}

The resolver runs during the wrapper's render

The resolver is a plain function call, not a component. Do not call hooks in the resolver body. Capture hook values in the enclosing component and close over them, as above.

Touch

New

Long-press a cell or a header for 500 ms to open its menu. The grid raises one contextmenu event, so onCellContextMenu fires as it does for a right-click. The browser's own touch menu stays closed. When the fill add-on is mounted, the cell menu of a touch selection also lists Fill down and Fill right. Their text comes from labels.contextMenu.fillDown and labels.contextMenu.fillRight.

Paste permission

Requires the clipboard-read permission

The Paste item needs clipboard-read. Browsers can refuse this without a user gesture inside the menu item itself. When permission is not available, the item degrades to a disabled state with a "press Ctrl+V" hint, instead of failing silently.

The paste semantics themselves (TSV/HTML formats, anchored expand, tiling) are documented on Clipboard.

Internationalization

All menu item labels come from the labels prop. labels.contextMenu carries the header items and the row-operation items, shared verbatim between the right-click menu and the header dropdown. See Internationalization: coverage by group.

On this page