Appearance
Selection
The option selection defines what the user can select: nothing ("None", default), rows ("Row") or cells ("Cell"). Row selection can be single or multiple, optionally with a checkbox column. Independent of the selection there is always a focused cell for keyboard navigation.
No selection
selection: "None" is the default. Clicking a cell only moves the focus – use it for read-only lists or when you react to clicks yourself (cell-click event).
Source
vue
<template>
<div class="demo">
<Datagrid :columns="columns" :rows="rows" :options="options" @cell-click="onCellClick" />
<div class="demo-log">{{ log }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type CellEventPayload, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const rows = createEmployees(200);
const log = ref("Click a cell or use the arrow keys – nothing gets selected, only the focus moves.");
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "city", text: "City" },
];
const options: GridOptions<Employee> = {
// default: no selection, the grid still has a focused cell for keyboard navigation
selection: "None",
defaultColumn: { flex: 1 },
};
function onCellClick({ data, column, value }: CellEventPayload<Employee>) {
log.value = `clicked ${column.id} of ${data.firstName} ${data.lastName}: ${value}`;
}
</script>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "city", text: "City" },
];
const options: GridOptions<Employee> = {
// default: no selection, the grid still has a focused cell for keyboard navigation
selection: "None",
defaultColumn: { flex: 1 },
};
export default function SelectionNone() {
const [log, setLog] = useState("Click a cell or use the arrow keys – nothing gets selected, only the focus moves.");
return (
<div className="demo">
<Datagrid
columns={columns}
rows={rows}
options={options}
onCellClick={({ data, column, value }) =>
setLog(`clicked ${column.id} of ${data.firstName} ${data.lastName}: ${value}`)
}
/>
<div className="demo-log">{log}</div>
</div>
);
}Single row
selection: "Row" with singleSelect: true selects one row at a time, e.g. for a list / detail layout. With emptySelectionAllowed: false the user can't deselect the row again.
Source
vue
<template>
<div class="demo">
<Datagrid
:columns="columns"
:rows="rows"
:options="options"
v-model:selectedItems="selected"
@selection-change="({ rows }) => (selectedRow = rows[0])"
/>
<div class="demo-log">
<template v-if="selectedRow">
selected: {{ selectedRow.firstName }} {{ selectedRow.lastName }}, {{ selectedRow.role }} in {{ selectedRow.department }}
</template>
<template v-else>nothing selected</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions, type ItemUid } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const rows = createEmployees(200);
const selected = ref<Array<ItemUid>>([3]);
const selectedRow = ref<Employee | undefined>(rows[2]);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "role", text: "Role" },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
// one row at a time, e.g. for a list / detail view
selection: "Row",
singleSelect: true,
// clicking the selected row again doesn't deselect it
emptySelectionAllowed: false,
defaultColumn: { flex: 1 },
};
</script>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions, type ItemUid } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "role", text: "Role" },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
// one row at a time, e.g. for a list / detail view
selection: "Row",
singleSelect: true,
// clicking the selected row again doesn't deselect it
emptySelectionAllowed: false,
defaultColumn: { flex: 1 },
};
export default function SelectionRow() {
const [selected, setSelected] = useState<Array<ItemUid>>([3]);
const [selectedRow, setSelectedRow] = useState<Employee | undefined>(rows[2]);
return (
<div className="demo">
<Datagrid
columns={columns}
rows={rows}
options={options}
selectedItems={selected}
onSelectedItemsChange={setSelected}
onSelectionChange={({ rows }) => setSelectedRow(rows[0])}
/>
<div className="demo-log">
{selectedRow
? `selected: ${selectedRow.firstName} ${selectedRow.lastName}, ${selectedRow.role} in ${selectedRow.department}`
: "nothing selected"}
</div>
</div>
);
}Multiple rows
With singleSelect: false the user can select several rows: Ctrl / ⌘ + click toggles a row, Shift + click selects a range, Ctrl + A selects all rows.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<button @click="api?.selectAll()">Select all</button>
<button @click="selected = []">Clear</button>
<span>{{ selected.length }} selected, total salary {{ total.toLocaleString("en") }} €</span>
</div>
<Datagrid
:columns="columns"
:rows="rows"
:options="options"
v-model:selectedItems="selected"
@ready="api = $event"
@selection-change="({ rows }) => (selectedRows = rows)"
/>
</div>
</template>
<script setup lang="ts">
import { computed, ref, shallowRef } from "vue";
import { Datagrid, type ColumnConfig, type Core, type GridOptions, type ItemUid } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const rows = createEmployees(200);
const api = shallowRef<Core>();
const selected = ref<Array<ItemUid>>([]);
const selectedRows = ref<Array<Employee>>([]);
const total = computed(() => selectedRows.value.reduce((sum, row) => sum + row.salary, 0));
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number" },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
// Ctrl / ⌘ + click toggles rows, Shift + click selects ranges
selection: "Row",
singleSelect: false,
defaultColumn: { flex: 1, sortable: true },
};
</script>tsx
import { useMemo, useRef, useState } from "react";
import {
Datagrid,
type ColumnConfig,
type DatagridHandle,
type GridOptions,
type ItemUid,
} from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number" },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
// Ctrl / ⌘ + click toggles rows, Shift + click selects ranges
selection: "Row",
singleSelect: false,
defaultColumn: { flex: 1, sortable: true },
};
export default function SelectionMulti() {
const grid = useRef<DatagridHandle>(null);
const [selected, setSelected] = useState<Array<ItemUid>>([]);
const [selectedRows, setSelectedRows] = useState<Array<Employee>>([]);
const total = useMemo(() => selectedRows.reduce((sum, row) => sum + row.salary, 0), [selectedRows]);
return (
<div className="demo">
<div className="demo-toolbar">
<button onClick={() => grid.current?.selectAll()}>Select all</button>
<button onClick={() => setSelected([])}>Clear</button>
<span>
{selected.length} selected, total salary {total.toLocaleString("en")} €
</span>
</div>
<Datagrid
ref={grid}
columns={columns}
rows={rows}
options={options}
selectedItems={selected}
onSelectedItemsChange={setSelected}
onSelectionChange={({ rows }) => setSelectedRows(rows)}
/>
</div>
);
}Checkbox selection
checkboxSelection: true adds a pinned column with checkboxes and a "select all" checkbox in the header. Clicking a checkbox toggles the row without changing the other selected rows. rowSelectable defines which rows can be selected – here only active employees.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<button @click="selected = [1, 2, 3]">Select rows 1–3</button>
<span>selected ids: {{ JSON.stringify(selected) }}</span>
</div>
<Datagrid :columns="columns" :rows="rows" :options="options" v-model:selectedItems="selected" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions, type ItemUid } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const rows = createEmployees(200);
const selected = ref<Array<ItemUid>>([]);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department", filterable: true },
{ field: "active", text: "Active (selectable)", type: "boolean", width: 150 },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
// pinned checkbox column with "select all" in the header
checkboxSelection: true,
// inactive employees cannot be selected
rowSelectable: (row) => row.data.active,
defaultColumn: { flex: 1 },
};
</script>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions, type ItemUid } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department", filterable: true },
{ field: "active", text: "Active (selectable)", type: "boolean", width: 150 },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
// pinned checkbox column with "select all" in the header
checkboxSelection: true,
// inactive employees cannot be selected
rowSelectable: (row) => row.data.active,
defaultColumn: { flex: 1 },
};
export default function SelectionCheckbox() {
const [selected, setSelected] = useState<Array<ItemUid>>([]);
return (
<div className="demo">
<div className="demo-toolbar">
<button onClick={() => setSelected([1, 2, 3])}>Select rows 1–3</button>
<span>selected ids: {JSON.stringify(selected)}</span>
</div>
<Datagrid
columns={columns}
rows={rows}
options={options}
selectedItems={selected}
onSelectedItemsChange={setSelected}
/>
</div>
);
}Cell ranges
selection: "Cell" selects cells like in a spreadsheet: drag with the mouse, Shift + arrow keys or Shift + click extend the range, Ctrl + click adds another range. The selected cells can be copied, cleared or filled. The status bar shows sum, average, minimum and maximum of the selected numbers. The column option cellSelectable excludes columns (here: ID).
Source
vue
<template>
<div class="demo">
<Datagrid :columns="columns" :rows="rows" :options="options" v-model:selectedCells="cells" />
<div class="demo-log">selected cells: {{ cells.length }} {{ JSON.stringify(cells.slice(0, 4)) }}{{ cells.length > 4 ? " …" : "" }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions, type ItemUid } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const rows = createEmployees(200);
const cells = ref<Array<{ item: ItemUid; column: string }>>([]);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70, cellSelectable: false },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "salary", text: "Salary", type: "number" },
{ field: "bonus", text: "Bonus", type: "number" },
{ field: "rating", text: "Rating", type: "number" },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
// drag with the mouse or use Shift + arrow keys, Ctrl + click adds ranges
selection: "Cell",
singleSelect: false,
// sum, average, min and max of the selected numbers
statusBar: true,
defaultColumn: { flex: 1 },
};
</script>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions, type ItemUid } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(200);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70, cellSelectable: false },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "salary", text: "Salary", type: "number" },
{ field: "bonus", text: "Bonus", type: "number" },
{ field: "rating", text: "Rating", type: "number" },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
// drag with the mouse or use Shift + arrow keys, Ctrl + click adds ranges
selection: "Cell",
singleSelect: false,
// sum, average, min and max of the selected numbers
statusBar: true,
defaultColumn: { flex: 1 },
};
export default function SelectionCell() {
const [cells, setCells] = useState<Array<{ item: ItemUid; column: string }>>([]);
return (
<div className="demo">
<Datagrid
columns={columns}
rows={rows}
options={options}
selectedCells={cells}
onSelectedCellsChange={setCells}
/>
<div className="demo-log">
selected cells: {cells.length} {JSON.stringify(cells.slice(0, 4))}
{cells.length > 4 ? " …" : ""}
</div>
</div>
);
}All modes in one grid
Switch the mode with the dropdown; the selected keys are bound to the parent state.
Source
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<label>
Mode
<select v-model="mode">
<option value="checkbox">Checkbox (multi)</option>
<option value="row">Row (single)</option>
<option value="cell">Cell range</option>
</select>
</label>
<button @click="selected = [1, 2, 3]">Select rows 1-3</button>
<span>selected keys: {{ JSON.stringify(selected) }}</span>
</div>
<Datagrid
:key="mode"
:columns="columns"
:rows="rows"
:options="options"
v-model:selectedItems="selected"
v-model:selectedCells="cells"
/>
<div class="demo-log">selected cells: {{ JSON.stringify(cells.slice(0, 20)) }}</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import {
Datagrid,
type ColumnConfig,
type GridOptions,
type ItemUid,
} from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const rows = createEmployees(500);
const mode = ref<"checkbox" | "row" | "cell">("checkbox");
const selected = ref<Array<ItemUid>>([]);
const cells = ref<Array<{ item: ItemUid; column: string }>>([]);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "active", text: "Active (selectable)", type: "boolean", width: 150 },
];
const options = computed<GridOptions<Employee>>(() => {
const base: GridOptions<Employee> = {
rowId: (data) => data.id,
// inactive employees cannot be selected
rowSelectable: (row) => row.data.active,
defaultColumn: { sortable: true, filterable: true, flex: 1 },
};
switch (mode.value) {
case "checkbox":
return { ...base, checkboxSelection: true };
case "row":
return { ...base, selection: "Row", singleSelect: true };
default:
return { ...base, selection: "Cell", singleSelect: false };
}
});
</script>tsx
import { useMemo, useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions, type ItemUid } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const rows = createEmployees(500);
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70 },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "active", text: "Active (selectable)", type: "boolean", width: 150 },
];
type Mode = "checkbox" | "row" | "cell";
export default function Selection() {
const [mode, setMode] = useState<Mode>("checkbox");
const [selected, setSelected] = useState<Array<ItemUid>>([]);
const [cells, setCells] = useState<Array<{ item: ItemUid; column: string }>>([]);
const options = useMemo<GridOptions<Employee>>(() => {
const base: GridOptions<Employee> = {
rowId: (data) => data.id,
// inactive employees cannot be selected
rowSelectable: (row) => row.data.active,
defaultColumn: { sortable: true, filterable: true, flex: 1 },
};
switch (mode) {
case "checkbox":
return { ...base, checkboxSelection: true };
case "row":
return { ...base, selection: "Row", singleSelect: true };
default:
return { ...base, selection: "Cell", singleSelect: false };
}
}, [mode]);
return (
<div className="demo">
<div className="demo-toolbar">
<label>
Mode{" "}
<select value={mode} onChange={(e) => setMode(e.target.value as Mode)}>
<option value="checkbox">Checkbox (multi)</option>
<option value="row">Row (single)</option>
<option value="cell">Cell range</option>
</select>
</label>
<button onClick={() => setSelected([1, 2, 3])}>Select rows 1-3</button>
<span>selected keys: {JSON.stringify(selected)}</span>
</div>
<Datagrid
key={mode}
columns={columns}
rows={rows}
options={options}
selectedItems={selected}
onSelectedItemsChange={setSelected}
selectedCells={cells}
onSelectedCellsChange={setCells}
/>
<div className="demo-log">selected cells: {JSON.stringify(cells.slice(0, 20))}</div>
</div>
);
}Options
| Option | Description |
|---|---|
selection: "None" | no selection (default) |
selection: "Row" | rows are selected by clicking |
selection: "Cell" | cells are selected, drag the mouse or use Shift + arrow keys to select ranges |
singleSelect | true (default) allows only one selected row / cell |
checkboxSelection | adds a pinned column with checkboxes and a "select all" checkbox in the header. Enables row selection with multiple rows. |
rowSelectable | (row) => boolean – which rows can be selected |
cellSelectable (column) | boolean or function – which cells can be selected |
emptySelectionAllowed | false prevents deselecting the last row |
Mouse and keyboard
- Click selects a row / cell.
- Ctrl / ⌘ + click adds or removes a row / cell (multi selection).
- Shift + click selects a range from the last clicked row / cell.
- Clicking a checkbox toggles the row without changing the selection of other rows. Shift + click selects the range.
- Space toggles the selection of the focused row.
- Shift + arrow keys extend the selection.
- Ctrl + A selects all rows / cells.
Binding the selection
vue
<Datagrid
v-model:selectedItems="selectedIds" // row ids
v-model:selectedCells="selectedCells" // [{ item: rowId, column: columnId }]
@selection-change="({ keys, rows, cells }) => ..."
/>tsx
<Datagrid
selectedItems={selectedIds} // row ids
onSelectedItemsChange={setSelectedIds}
selectedCells={selectedCells} // [{ item: rowId, column: columnId }]
onSelectedCellsChange={setSelectedCells}
onSelectionChange={({ keys, rows, cells }) => ...}
/>Both props are optional: without them the grid manages the selection itself.
Set a rowId to use your own ids. The selection event's rows contains the data objects of the selected rows.
API
ts
api.getSelectedData(); // data objects
api.getSelectedItems(); // row objects
api.getSelectedItemKeys(); // ids
api.getSelectedCells(); // [{ item, column }]
api.setSelectedRows([1, 2]);
api.setSelectedCells([{ item: 1, column: "name" }]);
api.toggleRowSelection(3);
api.selectAll();
api.deselectAll();
api.getSelectAllState(); // "all" | "some" | "none"selectAll() selects all rows matching the current filters.
Focus
Independent from the selection there is always one focused cell (the cell with the outline). It is moved with the arrow keys and used for editing and pasting.
ts
api.getFocusCell();
api.setFocusCell({ item: rowId, column: "name" });
api.focus(); // moves the browser focus into the grid