Selection Plugin
The Selection plugin adds cell, row, and range selection capabilities to the grid with full keyboard support. Whether you need simple cell highlighting or complex multi-range selections, this plugin has you covered.
Do you actually need this plugin?
Section titled “Do you actually need this plugin?”The Selection plugin exists to maintain a selection state — a set of cells, rows, or ranges — that other plugins can act on. It is the prerequisite for things like:
- ClipboardPlugin — copy/paste the current selection
- ExportPlugin — when
onlySelected: true, export only the rows the user has selected (without it, all rows are exported) - ContextMenuPlugin — when present, the context menu will operate on the multi-row selection instead of just the right-clicked row (the menu itself works fine without it)
- Any custom logic that needs to know “which rows/cells did the user pick?”
If all you want is to visually highlight the row the user has navigated to (the focused row), you do not need this plugin. The grid core already tracks keyboard focus on the active cell via the .cell-focus class — you can style the surrounding row with a small CSS snippet:
/* Highlight the row containing the focused cell, no plugin required */tbw-grid .data-grid-row:has(.cell-focus) { background-color: var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%));}
/* Optional: paint the focus tint over sticky/pinned cells too, which otherwise have an opaque background to mask scrolling content */tbw-grid .data-grid-row:has(.cell-focus) > .cell.sticky-left,tbw-grid .data-grid-row:has(.cell-focus) > .cell.sticky-right { background: linear-gradient(var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%)) 0 0), var(--tbw-color-panel-bg);}
/* Optional: suppress the per-cell focus outline if you want a row-only indicator. The grid core paints `outline: var(--tbw-focus-outline)` on `.cell-focus` while the grid has focus; leaving it ON makes it easier for keyboard users to see which cell they're on inside the highlighted row. Only add this rule if you specifically want the row to be the sole focus indicator (mimicking the Selection plugin's row mode).
The grid core sets the `data-has-focus` attribute on the host element whenever focus is inside the grid (managed by the focus controller), and removes it on blur — so this rule only suppresses the cell outline while the grid is actively focused. */tbw-grid[data-has-focus] .cell-focus { outline: none;}This mirrors what the Selection plugin does for row mode focus — without shipping the plugin’s selection-state machinery, keyboard range extension, or click handlers. Reach for the plugin only when something downstream needs to read or react to a selection.
Installation
Section titled “Installation”import '@toolbox-web/grid/features/selection';Basic Usage
Section titled “Basic Usage”import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/selection';
const grid = queryGrid('tbw-grid');grid.gridConfig = { columns: [ { field: 'id', header: 'ID' }, { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, ], features: { selection: 'row', },};grid.rows = data;import '@toolbox-web/grid-react/features/selection';import { DataGrid } from '@toolbox-web/grid-react';
function MyGrid({ data }) { return ( <DataGrid rows={data} columns={[ { field: 'id', header: 'ID' }, { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, ]} selection={{ mode: 'row' }} style={{ height: '400px' }} /> );}<script setup>import '@toolbox-web/grid-vue/features/selection';import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';const data = [ { id: 1, name: 'Alice', email: 'alice@example.com' }, { id: 2, name: 'Bob', email: 'bob@example.com' },];</script>
<template> <TbwGrid :rows="data" selection="row" style="height: 400px"> <TbwGridColumn field="id" header="ID" /> <TbwGridColumn field="name" header="Name" /> <TbwGridColumn field="email" header="Email" /> </TbwGrid></template>import { GridSelectionDirective } from '@toolbox-web/grid-angular/features/selection';import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { ColumnConfig } from '@toolbox-web/grid';
@Component({ selector: 'app-my-grid', imports: [Grid, GridSelectionDirective], template: ` <tbw-grid [rows]="rows" [columns]="columns" [selection]="'row'" style="height: 400px; display: block;"> </tbw-grid> `,})export class MyGridComponent { rows = [ { id: 1, name: 'Alice', email: 'alice@example.com' }, { id: 2, name: 'Bob', email: 'bob@example.com' }, ]; columns: ColumnConfig[] = [ { field: 'id', header: 'ID' }, { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, ];}Interactive Demo
Section titled “Interactive Demo”Switch between selection modes to see how each one behaves. The state panel below the grid shows the current selection in real time.
- Cell mode: Click cells to select them individually
- Row mode: Click anywhere in a row to select the entire row. Ctrl+Click to toggle, Shift+Click for range
- Range mode: Click and drag to select rectangular ranges. Ctrl+drag for multiple ranges
<tbw-grid style="height: 350px;"></tbw-grid> import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/selection';
const grid = queryGrid('tbw-grid'); const output = document.querySelector<HTMLElement>('[data-output-id="selection-demo"]');
if (grid) { const sampleData = [ { id: 1, name: 'Alice', department: 'Engineering', salary: 95000, email: 'alice@example.com' }, { id: 2, name: 'Bob', department: 'Marketing', salary: 75000, email: 'bob@example.com' }, { id: 3, name: 'Carol', department: 'Engineering', salary: 105000, email: 'carol@example.com' }, { id: 4, name: 'Dan', department: 'Sales', salary: 85000, email: 'dan@example.com' }, { id: 5, name: 'Eve', department: 'Marketing', salary: 72000, email: 'eve@example.com' }, { id: 6, name: 'Frank', department: 'Engineering', salary: 98000, email: 'frank@example.com' }, { id: 7, name: 'Grace', department: 'Sales', salary: 88000, email: 'grace@example.com' }, { id: 8, name: 'Henry', department: 'HR', salary: 65000, email: 'henry@example.com' }, ];
const columns = [ { field: 'id', header: 'ID', type: 'number' }, { field: 'name', header: 'Name' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'number' }, { field: 'email', header: 'Email' }, ];
function setupGrid(mode: string) { grid.gridConfig = { columns, features: { selection: mode } }; grid.rows = sampleData;
grid.on('selection-change', () => { const plugin = grid.getPluginByName('selection'); if (!plugin || !output) return; const sel = plugin.getSelection(); const lines = ['<b>mode:</b> ' + sel.mode];
if (sel.mode === 'row') { const indices = plugin.getSelectedRowIndices(); lines.push('<b>selectedRows:</b> [' + indices.join(', ') + ']'); if (indices.length > 0) { const names = indices.map((i: number) => sampleData[i]?.name).filter(Boolean); lines.push('<b>rowData:</b> ' + names.map((n: string) => '"' + n + '"').join(', ')); } }
if (sel.ranges.length > 0) { const rangeStrs = sel.ranges.map( (r: { from: { row: number; col: number }; to: { row: number; col: number } }) => '{ from: {row:' + r.from.row + ', col:' + r.from.col + '}, to: {row:' + r.to.row + ', col:' + r.to.col + '} }' ); lines.push('<b>ranges:</b> [' + rangeStrs.join(', ') + ']'); if (sel.mode === 'range') { lines.push('<b>cellCount:</b> ' + plugin.getSelectedCells().length); } } else { lines.push('<b>ranges:</b> []'); }
output.innerHTML = lines.join('<br>'); }); }
setupGrid('cell');
container.addEventListener('control-change', ((e: CustomEvent) => { setupGrid(e.detail.allValues.mode); })); }Checkbox Selection
Section titled “Checkbox Selection”Add checkbox: true to show a selection checkbox column with a “select all” header. Works exclusively in row mode.
<tbw-grid style="height: 250px;"></tbw-grid> import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/selection';
const grid = queryGrid('tbw-grid'); if (grid) { grid.gridConfig = { columns: [ { field: 'id', header: 'ID', type: 'number' }, { field: 'name', header: 'Name' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'number' }, ], features: { selection: { mode: 'row', checkbox: true } }, }; grid.rows = [ { id: 1, name: 'Alice', department: 'Engineering', salary: 95000 }, { id: 2, name: 'Bob', department: 'Marketing', salary: 75000 }, { id: 3, name: 'Carol', department: 'Engineering', salary: 105000 }, { id: 4, name: 'Dan', department: 'Sales', salary: 85000 }, { id: 5, name: 'Eve', department: 'Marketing', salary: 72000 }, ]; }features: { selection: { mode: 'row', checkbox: true } }Configuration Options
Section titled “Configuration Options”Grid-Level Toggle
Section titled “Grid-Level Toggle”You can disable selection grid-wide using gridConfig.selectable:
grid.gridConfig = { selectable: false, // Disables ALL selection features: { selection: 'range' },};Plugin Options
Section titled “Plugin Options”See SelectionConfig for the full list of options and defaults.
Double-Click Selection Trigger
Section titled “Double-Click Selection Trigger”In data-entry grids, you may want single-click to only focus the row/cell for keyboard navigation, while double-click changes the selection state.
features: { selection: { mode: 'row', triggerOn: 'dblclick', // Single-click focuses, double-click selects },}Conditional Selection
Section titled “Conditional Selection”Use the isSelectable callback to prevent selection of specific rows or cells:
features: { selection: { mode: 'row', isSelectable: (row) => row.status !== 'locked', },}Behavior of non-selectable rows/cells:
| Aspect | Behavior |
|---|---|
| Click | Ignored (no selection change) |
| Keyboard | Skipped with Shift+Arrow |
| Select All | Excluded |
| Visual | Muted via [data-selectable="false"] attribute |
| Focus | Still navigable |
Rows with status "locked" cannot be selected. Try clicking or using Shift+Click — locked rows are skipped.
<tbw-grid style="height: 300px;"></tbw-grid> import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/selection';
const grid = queryGrid('tbw-grid'); if (grid) { grid.gridConfig = { columns: [ { field: 'id', header: 'ID', type: 'number', width: 80 }, { field: 'name', header: 'Name' }, { field: 'department', header: 'Department' }, { field: 'status', header: 'Status' }, ], features: { selection: { mode: 'row', isSelectable: (row: { status: string }) => row.status !== 'locked', }, }, };
grid.rows = [ { id: 1, name: 'Alice Johnson', department: 'Engineering', status: 'active' }, { id: 2, name: 'Bob Smith', department: 'Marketing', status: 'locked' }, { id: 3, name: 'Carol Davis', department: 'Engineering', status: 'active' }, { id: 4, name: 'Dan Wilson', department: 'Sales', status: 'locked' }, { id: 5, name: 'Eve Brown', department: 'HR', status: 'active' }, { id: 6, name: 'Frank Miller', department: 'Engineering', status: 'active' }, { id: 7, name: 'Grace Lee', department: 'Finance', status: 'locked' }, { id: 8, name: 'Hank Taylor', department: 'Sales', status: 'active' }, ]; }Keyboard Shortcuts
Section titled “Keyboard Shortcuts”| Shortcut | Action |
|---|---|
Arrow Keys | Move focus |
Shift + Arrow | Extend selection (row and range modes) |
Shift + Page Up/Down | Extend selection by page (row and range modes) |
Shift + Ctrl/⌘ + Home/End | Extend selection to first/last row (row and range modes) |
Ctrl/⌘ + Click | Toggle row/cell (multi-select) |
Shift + Click | Extend selection from anchor |
Ctrl/⌘ + A | Select all (row and range modes) |
Escape | Clear selection |
Programmatic API
Section titled “Programmatic API”const plugin = grid.getPluginByName('selection');
// Queryconst selection = plugin.getSelection();plugin.isCellSelected(2, 1);
// Row modeplugin.selectRows([0, 2, 4]);const rows = plugin.getSelectedRows<Employee>();
// Range modeplugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: 5, col: 3 } }]);
// Actionsplugin.selectAll();plugin.clearSelection();Events
Section titled “Events”| Event | Description |
|---|---|
selection-change | Fired when the selection is modified |
<tbw-grid style="height: 300px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';
import '@toolbox-web/grid/features/selection';
const grid = queryGrid('tbw-grid');const logEl = document.querySelector('#selection-events-log');const clearBtn = document.querySelector('#selection-events-clear-btn');
grid.gridConfig = { columns: [ { field: 'id', header: 'ID', type: 'number', width: 60 }, { field: 'name', header: 'Name' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'number', align: 'right' }, ], features: { selection: 'range' },};
grid.rows = [ { id: 1, name: 'Alice Johnson', department: 'Engineering', salary: 85000 }, { id: 2, name: 'Bob Smith', department: 'Marketing', salary: 72000 }, { id: 3, name: 'Carol White', department: 'Sales', salary: 68000 }, { id: 4, name: 'David Brown', department: 'Engineering', salary: 92000 }, { id: 5, name: 'Eve Davis', department: 'HR', salary: 65000 },];
function addLog(msg: string) { const entry = document.createElement('div'); entry.className = 'event-log-entry'; entry.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; logEl.prepend(entry);}
grid.on('selection-change', (detail) => { addLog(`selection-change — ${detail.selectedCount ?? 0} cell(s) selected`);});
grid.on('row-select', (detail) => { addLog(`row-select — row ${detail.rowIndex}`);});
clearBtn?.addEventListener('click', () => { logEl.innerHTML = '';});selection-change Detail
Section titled “selection-change Detail”See SelectionChangeDetail and CellRange for the full event payload types.
Styling
Section titled “Styling”CSS Custom Properties
Section titled “CSS Custom Properties”| Property | Default | Description |
|---|---|---|
--tbw-focus-background | rgba(accent, 12%) | Focused row background |
--tbw-range-selection-bg | rgba(accent, 12%) | Range selection fill |
--tbw-range-border-color | var(--tbw-color-accent) | Range selection border |
--tbw-color-accent | #3b82f6 | Primary accent color |
tbw-grid { --tbw-range-selection-bg: rgba(76, 175, 80, 0.15); --tbw-range-border-color: #4caf50; --tbw-focus-background: rgba(76, 175, 80, 0.1);}CSS Classes
Section titled “CSS Classes”| Class | Element |
|---|---|
.selecting | Grid during range drag |
.row-focus | Focused row (row mode) |
.cell-focus | Focused cell (cell mode) |
.selected | Selected cell in range |
.selected.top / .bottom / .first / .last | Range boundary edges |
Works Well With
Section titled “Works Well With”| Plugin | Integration |
|---|---|
| EditingPlugin | Click-to-select + double-click-to-edit |
| ClipboardPlugin | Copy/paste selected cells (requires SelectionPlugin) |
| FilteringPlugin | Filter data, then select from results |
| ContextMenuPlugin | Right-click selected rows for actions |
See Also
Section titled “See Also”- Common Patterns — Full application recipes using selection
- Plugins Overview — All available plugins and compatibility
- Events Reference —
selection-changeevent details