Skip to content

Selection Patterns

Common patterns for implementing cell selection in your data grid.

Single Cell Selection

The simplest selection mode. Click to select one cell at a time:

tsx
const grid = useGridBehavior({
  rows,
  columns,
  config: {
    selection: { mode: "single" },
  },
});

Range Selection

Select rectangular regions of cells:

tsx
const grid = useGridBehavior({
  rows,
  columns,
  config: {
    selection: { mode: "range" },
  },
});

Keyboard Shortcuts

ShortcutAction
ClickSelect single cell
Shift+ClickExtend selection to clicked cell
Shift+ArrowExtend selection by one cell
Ctrl+ASelect all cells
EscapeClear selection

Row Selection with Checkboxes

For row-level selection, add a checkbox column:

tsx
const columns = [
  {
    id: "select",
    header: ({ table }) => (
      <input
        type="checkbox"
        checked={table.getIsAllRowsSelected()}
        onChange={table.getToggleAllRowsSelectedHandler()}
      />
    ),
    cell: ({ row }) => (
      <input
        type="checkbox"
        checked={row.getIsSelected()}
        onChange={row.getToggleSelectedHandler()}
      />
    ),
  },
  // ... other columns
];

Visual Feedback

Style selected cells for clear feedback:

tsx
function Cell({ grid, rowId, colId, children }) {
  const cell = useCellBehavior(grid, rowId, colId);

  return (
    <div
      {...cell.props}
      className={cn(
        "border border-transparent",
        cell.isFocused && "ring-2 ring-blue-500",
        cell.isSelected && "bg-blue-50 border-blue-200",
        cell.isFocused && cell.isSelected && "bg-blue-100",
      )}
    >
      {children}
    </div>
  );
}

Selection Bounds Indicator

Show a border around the entire selection:

tsx
function SelectionOverlay({ grid }) {
  const bounds = useStore(grid.store, selectSelectionBounds);

  if (!bounds) return null;

  // Calculate pixel positions from bounds
  const style = {
    position: "absolute",
    top: bounds.startRow * rowHeight,
    left: bounds.startCol * colWidth,
    width: (bounds.endCol - bounds.startCol + 1) * colWidth,
    height: (bounds.endRow - bounds.startRow + 1) * rowHeight,
    border: "2px solid blue",
    pointerEvents: "none",
  };

  return <div style={style} />;
}

Selection Change Callback

React to selection changes:

tsx
const grid = useGridBehavior({
  rows,
  columns,
  onSelectionChange: ({ ranges }) => {
    // ranges is an array of { start, end } cell references
    const selectedCells = calculateSelectedCells(ranges);
    console.log("Selected:", selectedCells);

    // Example: update external state
    setSelectedData(
      selectedCells.map(({ rowId, colId }) => data.find((row) => row.id === rowId)?.[colId]),
    );
  },
});

Copy Selection to Clipboard

Handle Ctrl+C to copy selected data:

tsx
const grid = useGridBehavior({
  rows,
  columns,
  config: {
    clipboard: {
      serialize: (cells) => {
        // Custom serialization
        return cells.map((row) => row.map((cell) => cell.value).join("\t")).join("\n");
      },
    },
  },
});

Clear Selection on Navigation

Optionally clear selection when navigating without Shift:

tsx
const grid = useGridBehavior({
  rows,
  columns,
  config: {
    selection: {
      mode: "range",
      clearOnNavigation: true, // Arrow keys clear selection
    },
  },
});

Programmatic Selection

Select cells programmatically:

tsx
function SelectAll({ grid }) {
  const handleSelectAll = () => {
    grid.dispatch({ type: "SELECT_ALL" });
  };

  const handleClearSelection = () => {
    grid.dispatch({ type: "CLEAR_SELECTION" });
  };

  const handleSelectRange = () => {
    grid.dispatch({
      type: "SELECT_RANGE",
      start: { rowId: "row-0", colId: "col-0" },
      end: { rowId: "row-5", colId: "col-2" },
    });
  };

  return (
    <div>
      <button onClick={handleSelectAll}>Select All</button>
      <button onClick={handleClearSelection}>Clear</button>
      <button onClick={handleSelectRange}>Select Range</button>
    </div>
  );
}

Selection Persistence

Selection persists when focus leaves the grid. This enables:

  • Context menu operations on selection
  • Copy/paste after clicking elsewhere
  • Multi-step workflows

To explicitly clear:

tsx
// Clear on blur (not recommended, but possible)
const grid = useGridBehavior({
  rows,
  columns,
  onBlur: () => {
    grid.dispatch({ type: "CLEAR_SELECTION" });
  },
});

See Also

Released under the MIT License.