DataGrid
Headless compound for tabular data with column layout, cell editing, row ordering, and row spanning.
Usage
DataGrid.Root creates the grid. DataGrid.Column and DataGrid.Row register when they mount and unregister when they unmount — same lifecycle as Checkbox.Group. A resizable header row composes Splitter so DataGrid.Handle resizes columns. v-for="user in rank(users)". Non-renderless Row v-shows off-page rows — don’t add a consumer v-show. Renderless: v-show="isVisible" on your host. The client adapter defaults to 10 rows per page. Compose Pagination or pass :pagination="{ itemsPerPage: n }" on Root.
Anatomy
<script setup lang="ts">
import { DataGrid } from '@vuetify/v0'
</script>
<template>
<DataGrid.Root>
<DataGrid.Table>
<DataGrid.Header>
<DataGrid.Row>
<DataGrid.Column>
<DataGrid.Handle />
</DataGrid.Column>
</DataGrid.Row>
</DataGrid.Header>
<DataGrid.Body>
<DataGrid.Row>
<DataGrid.Cell />
</DataGrid.Row>
<DataGrid.Empty>
<DataGrid.Cell />
</DataGrid.Empty>
</DataGrid.Body>
</DataGrid.Table>
</DataGrid.Root>
</template>Architecture
DataGrid extends DataTable with four additional capabilities:
Layout — Column pinning, sizing, resizing, and reordering via
context.layoutRows — Manual row ordering via
context.rows.move()andcontext.rows.reset().rankis on the Body slot, ranks byorderedItemssomove()is visible; Row hides the page.Editing — Cell editing state via
context.editingSpans — Row spanning via
context.spans
Layout API
The context.layout API provides column sizing primitives:
layout.resize(columnId, delta)— Resize a column by a percentage delta (composable neighbor-delta API; Handle does not call this)layout.distribute(sizes)— Set visible-column sizes at once in Column DOM/registry order (what a resizable row’s Splitter@layoutcalls). Notlayout.columnspin order.layout.columns— Reactive array of column layout state includingsize,minSize,maxSizelayout.pin(columnId, 'left' | 'right' | false)— Pin or unpin at runtime
Examples
Name is pinned left, status right. Press L / R / · on a header to cycle pin. Scroll sideways.
Name | Owner | Team | Repo | Pts | Updated | Id | Status |
|---|---|---|---|---|---|---|---|
| Auth refresh | Alice | Platform | vuetifyjs/0 | 5 | Aug 21 | 1 | Doing |
| Docs ranking | Bob | Docs | vuetifyjs/docs | 3 | Aug 18 | 2 | Todo |
| Column pin | Carol | Design | vuetifyjs/0 | 8 | Aug 22 | 3 | Doing |
| Empty copy | David | Docs | vuetifyjs/docs | 2 | Aug 12 | 4 | Done |
| Pager keys | Eve | Platform | vuetifyjs/0 | 3 | Aug 19 | 5 | Todo |
| Sticky offset | Frank | Design | vuetifyjs/0 | 5 | Aug 20 | 6 | Todo |
| Cell commit | Grace | Platform | vuetifyjs/0 | 5 | Aug 22 | 7 | Doing |
| Row index | Henry | Docs | vuetifyjs/0 | 2 | Aug 11 | 8 | Done |
Accessibility
DataGrid ships structural table roles. It is not a WAI-ARIA Grid APG widget — there is no roving tabindex, aria-activedescendant, or keyboard cell navigation. Name it with aria-label or a <caption> — Root is a fragment and cannot be named.
DataGrid.Tablerenders<table role="table">.aria-rowcountis set only when the current page is a subset of total (count includes header rows).DataGrid.Rowsetsaria-rowindexfrom its position inorderedItemsunless:indexis passed. Header rows auto-number from Header child order;:indexoverrides.DataGrid.HeaderandDataGrid.Bodyomitroleon nativethead/tbody;as="div"getsrole="rowgroup".DataGrid.Rowrenders withrole="row"andaria-selectedwhenselectableis setDataGrid.Columnrenders withrole="columnheader",scope="col", andaria-sorton sortable columnsDataGrid.Cellrenders withrole="cell"androwspan(oraria-rowspanwhenasis nottd) for spanned cells
Put a Button.Root inside sortable header cells — do not make the <th> itself the control:
<template>
<DataGrid.Column
id="name"
v-slot="{ isSortable, toggle }"
>
<Button.Root v-if="isSortable" @click="toggle">
Name
</Button.Root>
<span v-else>Name</span>
</DataGrid.Column>
</template>DataGrid.Handle (inside a resizable row) inherits Splitter’s role="separator" semantics. Nest it inside DataGrid.Column so the separator is a descendant of columnheader, not a sibling under role="row" — a row may only own cell, columnheader, rowheader, or gridcell.
FAQ
DataGrid adds column layout (pinning, sizing), cell editing, row ordering, and row spanning on top of DataTable. Use DataGrid when you need spreadsheet-like editing or row reordering; use DataTable for read-only tabular data.
Call editing.edit(rowId, columnId), then commit(value) or cancel(). onEdit is 4-arg (row, column, value, item) and runs after a successful commit. DataGrid.Cell is display-only — it exposes an isEditing flag, not an editor. DataGrid.Row must use the same id as the ticket. See the composable editing example.
Pass new ServerGridAdapter({ total, loading?, error? }) — there is no fetch. Onboard the current page of rows; the server owns sort, filter, and pagination.
Runtime pin is layout.pin(columnId, 'left' | 'right'). Unpin with false. ticket.pinned is a registration snapshot; slot pinPosition / isPinned read layout.columns.
A per-column span(item) function on the column ticket wins over Root rowSpanning. Spanned cells are hidden via v-if in DataGrid.Cell.
Set resizable on the header row and nest DataGrid.Handle inside each column except the last. Handle → Splitter @layout → layout.distribute. layout.resize(id, delta) is the composable neighbor-delta API, not what Handle calls. Splitter cannot live in a native table — use the full as="div" chain. See the usage example.