Data Grid
Accessible, unstyled DataGrid (WAI-ARIA APG grid / treegrid): typed column definitions, multi-column sorting, filtering hooks, column resize, reorder, pinning and visibility, row selection, cell navigation and editing, tree rows, and optional row virtualization.
|
|
| Package |
@vizejs/ui/data-grid |
| Maturity |
stable |
| Own the source |
vize lib pull data-grid |
| Requires |
context, controllable-state, id, virtualizer |
| Aliases |
data table, spreadsheet grid, tree grid, editable table |
| Covers |
WAI-ARIA grid pattern, WAI-ARIA treegrid pattern, TanStack Table, AG Grid, React Aria Table |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { DataGrid, DataGridCell, DataGridCellEditor, DataGridColumnHeader, DataGridResizeHandle, DataGridRow } from "@vizejs/ui/data-grid";
Or copy the source into your project with vize lib pull data-grid (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Sortable, multi-select employee grid with typed columns and controlled sorting and selection. -->
<script setup lang="ts">
import { ref } from "vue";
import { createColumnHelper, DataGrid } from "@vizejs/ui/data-grid";
import type { DataGridSort } from "@vizejs/ui/data-grid";
interface Employee {
readonly id: string;
readonly name: string;
readonly role: string;
readonly salary: number;
}
const employees: readonly Employee[] = [
{ id: "e1", name: "Ada Lovelace", role: "Engineer", salary: 128000 },
{ id: "e2", name: "Grace Hopper", role: "Architect", salary: 154000 },
{ id: "e3", name: "Alan Turing", role: "Researcher", salary: 141000 },
{ id: "e4", name: "Katherine Johnson", role: "Analyst", salary: 119000 },
];
const column = createColumnHelper<Employee>();
const columns = [
column.accessor("name", { header: "Name", width: 200 }),
column.accessor("role", { header: "Role" }),
column.accessor("salary", { header: "Salary", align: "end" }),
];
const rowId = (row: Employee): string => row.id;
const sorting = ref<readonly DataGridSort[]>([{ columnId: "name", direction: "ascending" }]);
const selection = ref<readonly string[]>([]);
</script>
<template>
<div>
<DataGrid
v-model:sorting="sorting"
v-model:selection="selection"
:rows="employees"
:columns
:get-row-id="rowId"
selection-mode="multiple"
aria-label="Employees"
/>
<output>{{ selection.length }} selected</output>
</div>
</template>
API
DataGrid
Source: src/families/data/data-grid/data-grid.vue
Props
| Prop |
Type |
Default |
Description |
rows (required) |
readonly Row[] |
undefined |
Source rows. Nested rows come from getSubRows. |
columns (required) |
readonly Column[] |
undefined |
Column definitions, usually built with createColumnHelper<Row>(). |
id |
string | null |
undefined |
Consumer-owned grid id. null and undefined select a deterministic fallback. |
getRowId |
(row: Row, index: number, parentId: string | null) => string |
undefined |
Stable row id; defaults to the index path ("3", "3.1"). |
getSubRows |
(row: Row) => readonly Row[] | null | undefined |
undefined |
Sub rows; providing it renders role="treegrid". |
filterRow |
(row: Row) => boolean |
undefined |
Extra row predicate combined with column and global filters. |
selectionMode |
DataGridSelectionMode |
"none" |
Row selection policy. |
pageSize |
number |
10 |
Rows moved by PageUp and PageDown. |
dir |
"ltr" | "rtl" |
"ltr" |
Reading direction for horizontal arrow keys. |
virtualize |
boolean |
false |
Render only the rows in view (the root element is the scroll container). |
rowHeight |
number |
36 |
Fixed row height in CSS pixels used by virtualization. |
overscan |
number |
4 |
Extra virtualized rows rendered above and below the viewport. |
initialViewportHeight |
number |
400 |
Viewport height assumed during SSR and before measurement. |
ariaLabel |
string |
undefined |
Accessible name of the grid. |
ariaLabelledby |
string |
undefined |
Ids that label the grid. |
defaultState |
Partial<DataGridState> |
undefined |
Initial uncontrolled state slices. |
sorting |
readonly DataGridSort[] |
undefined |
Controlled sort keys (v-model:sorting). |
globalFilter |
string |
undefined |
Controlled global filter text (v-model:global-filter). |
columnFilters |
Readonly<Record<string, unknown>> |
undefined |
Controlled per-column filter values (v-model:column-filters). |
columnVisibility |
Readonly<Record<string, boolean>> |
undefined |
Controlled column visibility (v-model:column-visibility). |
columnOrder |
readonly string[] |
undefined |
Controlled column order (v-model:column-order). |
columnPinning |
DataGridPinning |
undefined |
Controlled pinning (v-model:column-pinning). |
columnSizing |
Readonly<Record<string, number>> |
undefined |
Controlled widths (v-model:column-sizing). |
selection |
readonly string[] |
undefined |
Controlled selected row ids (v-model:selection). |
expanded |
readonly string[] |
undefined |
Controlled expanded row ids (v-model:expanded). |
Events
| Event |
Payload |
Description |
update:sorting |
[value: readonly DataGridSort[]] |
Fired when sorting changes. |
update:globalFilter |
[value: string] |
Fired when the global filter changes. |
update:columnFilters |
[value: Readonly<Record<string, unknown>>] |
Fired when a column filter changes. |
update:columnVisibility |
[value: Readonly<Record<string, boolean>>] |
Fired when column visibility changes. |
update:columnOrder |
[value: readonly string[]] |
Fired when the column order changes. |
update:columnPinning |
[value: DataGridPinning] |
Fired when pinning changes. |
update:columnSizing |
[value: Readonly<Record<string, number>>] |
Fired when a column is resized. |
update:selection |
[value: readonly string[]] |
Fired when the row selection changes. |
update:expanded |
[value: readonly string[]] |
Fired when rows expand or collapse. |
cell-edit |
[event: DataGridCellEditEvent<Row>] |
Fired when a cell edit commits; update rows to apply it. |
Slots
| Slot |
Slot props |
Description |
header |
DataGridHeaderSlotProps<Row, Column> |
Column header contents. Defaults to the column's header or id. |
cell |
DataGridCellSlotProps<Row, Column> |
Cell contents, discriminated by columnId. Defaults to the formatted value. |
editor |
DataGridEditorSlotProps<Row> |
Editor for the cell being edited. Defaults to a text input. |
empty |
— |
Rendered in the body when no rows remain after filtering. |
Exposed
| Member |
Type |
Description |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
controller |
— |
|
focusCell |
— |
|
scrollToRow |
— |
|
DataGridCell
Source: src/families/data/data-grid/data-grid-cell.vue
Props
| Prop |
Type |
Default |
Description |
rowId (required) |
string |
undefined |
Row id this cell belongs to. |
columnId (required) |
string |
undefined |
Column id this cell belongs to. |
colIndex (required) |
number |
undefined |
One-based aria-colindex. |
width |
number |
150 |
Column width in CSS pixels, published as --vize-ui-data-grid-column-width. |
pin |
DataGridPinSide | null |
null |
Pin side, published as data-pin. |
pinOffset |
number |
0 |
Sticky inset from the pinned edge, published as --vize-ui-data-grid-pin-offset. |
align |
DataGridAlign |
"start" |
Logical alignment hook. |
editable |
boolean |
false |
Whether the cell can be edited (Enter/F2 or double click). |
Slots
| Slot |
Slot props |
Description |
default |
— |
Cell contents rendered by the grid root. |
Exposed
| Member |
Type |
Description |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
DataGridCellEditor
Source: src/families/data/data-grid/data-grid-cell-editor.vue
Props
| Prop |
Type |
Default |
Description |
label |
string |
undefined |
Accessible name of the editor input. |
Exposed
| Member |
Type |
Description |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
DataGridColumnHeader
Source: src/families/data/data-grid/data-grid-column-header.vue
Props
| Prop |
Type |
Default |
Description |
columnId (required) |
string |
undefined |
Column id. |
colIndex (required) |
number |
undefined |
One-based aria-colindex. |
label |
string |
undefined |
Header text, also the resize handle's accessible name. |
width |
number |
150 |
Column width in CSS pixels. |
minWidth |
number |
40 |
Minimum width announced by the resize handle. |
maxWidth |
number |
Infinity |
Maximum width announced by the resize handle. |
pin |
DataGridPinSide | null |
null |
Pin side. |
pinOffset |
number |
0 |
Sticky inset from the pinned edge. |
align |
DataGridAlign |
"start" |
Logical alignment hook. |
sortable |
boolean |
false |
Whether clicking, Enter, or Space toggles sorting (Shift adds a sort key). |
sortDirection |
DataGridSortDirection | null |
null |
Current sort direction. |
sortIndex |
number | null |
null |
One-based priority among sort keys. |
resizable |
boolean |
false |
Render a pointer and keyboard resize handle. |
Slots
| Slot |
Slot props |
Description |
default |
— |
Header contents rendered by the grid root. |
DataGridResizeHandle
Source: src/families/data/data-grid/data-grid-resize-handle.vue
Props
| Prop |
Type |
Default |
Description |
columnId (required) |
string |
undefined |
Column id resized by this handle. |
label |
string |
undefined |
Column label used in the handle's accessible name. |
width |
number |
150 |
Current width, published as aria-valuenow. |
minWidth |
number |
40 |
Minimum width, published as aria-valuemin. |
maxWidth |
number |
Infinity |
Maximum width, published as aria-valuemax when finite. |
step |
number |
10 |
Pixels per Arrow key press. |
DataGridRow
Source: src/families/data/data-grid/data-grid-row.vue
Props
| Prop |
Type |
Default |
Description |
rowId (required) |
string |
undefined |
Row id. |
rowIndex (required) |
number |
undefined |
One-based aria-rowindex (the header row is 1). |
selected |
boolean |
false |
Whether the row is selected. |
depth |
number |
0 |
Zero-based tree depth, published as aria-level in tree grids. |
expandable |
boolean |
false |
Whether the row has sub rows. |
expanded |
boolean |
false |
Whether the sub rows are shown. |
start |
number | null |
null |
Virtualized offset in CSS pixels, published as --vize-ui-data-grid-row-start. null means the row is laid out in normal flow. |
Slots
| Slot |
Slot props |
Description |
default |
— |
Row cells rendered by the grid root. |
Behavior
@vizejs/ui/data-grid implements the WAI-ARIA APG data grid
and treegrid patterns. data-grid.vue is the generic
root (Row, Column); data-grid-row.vue, data-grid-cell.vue, data-grid-column-header.vue,
data-grid-resize-handle.vue, and data-grid-cell-editor.vue are value-erased parts. The headless model
(useDataGrid) and pure row pipeline are proven in data-grid-model.test.ts; mounted behavior in
data-grid.test.ts; SSR in data-grid-ssr.test.ts; column inference in data-grid.types.test-d.ts.
| # |
State |
Input |
Outcome |
Proven by |
| G1 |
any |
render |
role="grid", aria-rowcount/aria-colcount, header columnheaders with aria-sort, rows with aria-rowindex, typed cell slots |
renders APG grid roles, counts, indices, and typed cell slots |
| G2 |
sortable header |
click / Shift+click |
ascending → descending → off; Shift adds lower-priority keys; update:sorting fires |
header clicks sort ascending, descending, then off; Shift adds sort keys |
| G3 |
sorted |
null/undefined values |
missing values sort last in both directions |
missing values sort last in both directions |
| G4 |
filters |
global, column filter, filterRow |
rows narrow; data-empty and the empty slot render when nothing matches |
global, column, and custom filters narrow rows and show the empty slot |
| G5 |
focus in grid |
arrows, Home/End, Ctrl+Home/End, Page keys |
one roving tab stop moves across header and body cells, clamped at edges |
arrow, Home/End, Ctrl+Home/End, and Page keys move a single roving tab stop |
| G6 |
dir="rtl" |
ArrowLeft/ArrowRight |
horizontal movement flips |
rtl flips horizontal cell movement |
| G7 |
header focus |
Enter/Space, Ctrl+Shift+arrows, Alt+arrows |
toggles sort, moves the column (focus follows), resizes by 10 px |
header keys sort, reorder with Ctrl+Shift+arrows, and resize with Alt+arrows |
| G8 |
resizable column |
handle pointer drag / arrows |
role="separator" with value range; width follows the pointer, clamps to minWidth/maxWidth, publishes width CSS variables |
resize handles are labelled separators driven by pointer and arrows within limits |
| G9 |
layout state |
visibility, order, pinning, sizing |
hidden columns disappear, order applies, pinned columns group at each edge with sticky offsets and total width variables |
visibility, order, and pinning props lay out columns with sticky offsets |
| G10 |
selectionMode="multiple" |
click/Ctrl/Shift, Space, Shift+Arrow, Ctrl+A |
replace, toggle, range, keyboard toggle/extend, select all; aria-selected and aria-multiselectable |
multiple selection: click replaces, Ctrl toggles, Shift extends, Space and Ctrl+A |
| G11 |
single / none |
click |
single keeps one row; none publishes no aria-selected |
single selection keeps one row and none mode publishes no aria-selected |
| G12 |
editable cell |
Enter/F2/double click, typing, Enter/Tab/Escape |
editor takes focus; validate errors keep it open with aria-invalid; parse converts; cell-edit fires; focus returns to the cell |
Enter or F2 edits, Enter commits through parse and validate, Escape cancels |
| G13 |
read-only cell |
Enter |
nothing happens; aria-readonly="true" |
read-only cells never enter edit mode |
| G14 |
getSubRows |
ArrowRight/ArrowLeft on the first cell |
role="treegrid" with aria-level/aria-expanded; expand, collapse, move to parent; filters keep ancestors |
tree rows render a treegrid with levels; arrows expand, collapse, and climb |
| G15 |
virtualize |
render / Ctrl+End |
only a window of rows renders with absolute aria-rowindex and offset variables; focus scrolls the target row in |
virtualization renders a window with absolute row indices and scrolls to focus |
| G16 |
custom layouts |
import |
parts are exported |
exported parts stay importable for custom layouts |
| G17 |
model |
sort toggles |
cycle and multi-key priority |
toggleSorting cycles ascending, descending, off and keeps multi-key priority |
| G18 |
model |
dot paths |
nullish segments short-circuit |
readPath short-circuits nullish segments and ignores non-objects |
| G19 |
model |
tree pipeline |
depth, parents, selection, expansion, stable sort within levels |
buildRowModels flattens expanded trees with depth, parents, and stable sort |
| G20 |
model |
default compare |
locale- and numeric-aware ordering |
custom compare and default string comparison are locale and numeric aware |
| G21 |
model, uncontrolled |
state actions |
every slice updates and reports through onStateChange; hideable/resizable/display-column rules hold |
uncontrolled slices update state and report every change |
| G22 |
model, controlled |
state actions |
controlled slices only change through the owner |
controlled slices only change through the owner |
| G23 |
model |
edit commit |
parse errors surface, validation gates, rows are never mutated |
edits parse, validate, report parse errors, and never mutate rows |
| G24 |
SSR |
two requests |
byte-identical markup including default sorting and selection |
renders byte-identical sorted, selected grid markup across SSR requests |
| G25 |
SSR |
hydrate plain/tree/virtual |
no diagnostics; server root reused |
hydrates plain, tree, and virtualized grids without diagnostics |
Extension hooks
| Hook |
Values |
| parts |
root, header, header-row, column-header, resize-handle, body, row, cell, cell-editor, empty |
| data |
data-vize-ui, data-row-id, data-column-id, data-sort, data-sort-index, data-pin, data-align, data-active, data-editing, data-selected, data-depth, data-virtualized |
| CSS vars |
--vize-ui-data-grid-column-width, --vize-ui-data-grid-pin-offset, --vize-ui-data-grid-total-width, --vize-ui-data-grid-body-height, --vize-ui-data-grid-row-start |