Skip to main content
Vuetify0 is now in beta!
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

createDataGrid

A headless data grid with column layout, cell editing, row ordering, and row spanning.

Usage

Construct a grid, onboard columns through grid.columns with size percentages, then register rows to get column layout, search, sort, and pagination. Columns are onboarded — not passed as a factory option — so the surface matches createDataTable and columns can be added or removed at any time.

ts
import { createDataGrid } from '@vuetify/v0'

const grid = createDataGrid()

// Onboard columns through the inherited column registry
grid.columns.onboard([
  { id: 'name', title: 'Project', sortable: true, filterable: true, size: 22 },
  { id: 'status', title: 'Status', sortable: true, size: 12 },
  { id: 'assignee', title: 'Assignee', sortable: true, size: 16 },
  { id: 'progress', title: 'Progress', sortable: true, size: 14 },
  { id: 'budget', title: 'Budget', sortable: true, size: 10 },
])

// Register rows through the inherited registry surface
grid.onboard(projects.map(value => ({ id: value.id, value })))

// Inherited from createDataTable
grid.search('alice')
grid.sort.toggle('name')
grid.pagination.next()

// Grid-specific: column layout
grid.layout.columns.value    // ResolvedColumn[] with size, offset, pinned
grid.layout.pin('name', 'left')
grid.layout.resize('name', 5) // grow by 5%, neighbor shrinks
grid.layout.reorder(0, 2)     // move column 0 to position 2
grid.layout.hide('budget')    // exclude from the render set
grid.layout.reset()            // restore initial layout

Architecture

createDataGrid composes createDataTable — which owns the data pipeline (filter, sort, paginate) — with four grid modules: column layout, cell editing, row ordering (createSortable), and row spanning. Columns and rows are both onboarded through registries (grid.columns.onboard(...), grid.onboard(...)) rather than passed as options, the same shape as createDataTable. Per-column config (size, pinned, editable, validate, span) rides on each column ticket, so layout, editing, and spanning read it straight off grid.columns and pick up columns onboarded at any time.

createDataGrid Architecture

Use controls to zoom and pan. Click outside or press Escape to close.

createDataGrid Architecture
ModuleBuilt onPurpose
table (spread)createDataTableSearch, sort, filter, paginate, total — all v-modeled through
layoutgrid.columns + createGroupReads column order and config from the column registry; layers tri-region pinning, percentage sizing, delta-based resize, and visibility (show / hide / toggle / all) on top
editinginternal factoryClick-to-edit lifecycle, per-column validation, dirty tracking
rowscreateSortablePost-sort row reordering, layered in the grid’s items projection over the sorted rows before pagination — not inside the adapter
spanscomputed mapRow span resolution and hidden-cell tracking

Adapters

The grid uses the standard data table adapters — row ordering is layered above the pipeline, not inside it, so any DataTableAdapter works without modification.

AdapterPipelineUse Case
ClientDataTableAdapter (default)filter → sort → paginateAll processing client-side
ServerGridAdapterpass-throughAPI-driven. Server handles everything
VirtualDataTableAdapterfilter → sort → (no paginate)Large lists with createVirtual
Diagram

Use controls to zoom and pan. Click outside or press Escape to close.

ts
import { createDataGrid } from '@vuetify/v0'

const grid = createDataGrid()
// ClientDataTableAdapter is the default — no adapter option required

grid.columns.onboard(columns)
grid.onboard(employees.map(value => ({ id: value.id, value })))

// Row ordering — id-based
grid.rows.move(employees[0].id, 3)  // move that row to position 3
grid.rows.reset()                    // clear custom ordering

ServerGridAdapter

Pass-through adapter for API-driven grids — the server owns sort, filter, and pagination. Re-exports the data table’s ServerDataTableAdapter.

Onboard only the rows the server returns for the current page, and set total to the full server-side count. The grid renders that page on every page — onboard page 2’s rows, advance pagination.page, and the grid surfaces them. Because the page window can sit past the locally-held rows, the grid orders the onboarded page in place rather than re-slicing it into emptiness.

ts
import { createDataGrid, ServerGridAdapter } from '@vuetify/v0'

const grid = createDataGrid({
  adapter: new ServerGridAdapter({ total: totalCount, loading: isLoading }),
})

grid.columns.onboard(columns)

// On every page change, clear and onboard the rows the server returned for
// that page; keep `total` at the full server count. `grid.clear()` wipes
// the row registry — onboarded columns are untouched.
grid.clear()
grid.onboard(rows.map(value => ({ id: value.id, value })))

Virtual scrolling

For large datasets, use the standard VirtualDataTableAdapter. Row ordering still applies; pagination slicing is skipped.

ts
import { createDataGrid, VirtualDataTableAdapter } from '@vuetify/v0'

const grid = createDataGrid({
  adapter: new VirtualDataTableAdapter(),
})

grid.columns.onboard(columns)
grid.onboard(largeDataset.map(value => ({ id: value.id, value })))

Reactivity

PropertyReactiveNotes
itemsFinal visible items (filter + sort + row order + paginate)
allItemsRaw unprocessed items (projected from registered tickets)
filteredItemsItems after filtering
sortedItemsItems after filter + sort
layout.columnsResolved columns with size/offset (render set — visible only)
layout.allEvery column incl. hidden, each with a visible flag
layout.pinnedPin region breakdown
editing.activeCurrently edited cell
editing.errorValidation error string
editing.dirtyUncommitted edits map
rows.orderCurrent row ordering
spansRow span map
headers2D header grid
sort.columnsCurrent sort entries
pagination.pageCurrent page
totalTotal row count

Examples

Column Pinning & Resizing

A financial data grid with 10 columns that requires horizontal scrolling. Ticker is pinned left, sector pinned right — the center columns scroll independently with drag-to-resize handles.

File breakdown:

FileRole
data.ts12 stocks across Tech, Healthcare, Finance, Energy, and Consumer sectors
columns.ts10 columns with ticker pinned left, sector pinned right
usePinnedGrid.tsLayout logic — pin/unpin, drag-resize, pixel sticky offsets, and the up/down/volume stats
PinnedToolbar.vueMarket-overview header with the ticker filter and reset
PinnedFooter.vueRow count plus the left / scrollable / right pin-region summary
PinnedGrid.vueThe spreadsheet table — sticky pinned columns, resize handles, formatted numbers

Key patterns:

  • layout.pinned splits columns into left, scrollable, and right regions with independent offsets

  • layout.resize(id, delta) adjusts a column and its neighbor to maintain total width

  • layout.pin(id, position) moves columns between regions dynamically

  • layout.reset() restores initial sizes, order, and pins

Market overview5↑ · 5↓ · Vol 193.4M
Ticker
Company
Price
Change
Volume
Mkt Cap
P/E
EPS
Div %
Sector
AAPLApple Inc. $198.11+1.24% 54.3M$3.1T32.4 $6.110.55% Tech
MSFTMicrosoft Corp. $452.37-0.38% 22.1M$3.4T37.1 $12.200.72% Tech
GOOGLAlphabet Inc. $176.89+2.15% 28.7M$2.2T25.6 $6.910.00% Tech
JNJJohnson & Johnson $155.42-0.72% 8.4M$374.0B15.8 $9.842.96% Healthcare
UNHUnitedHealth Group $527.63+0.89% 3.2M$486.0B21.3 $24.771.42% Healthcare
PFEPfizer Inc. $27.14-1.53% 41.6M$153.0B45.2 $0.605.88% Healthcare
JPMJPMorgan Chase $205.88+0.67% 9.8M$592.0B12.1 $17.022.34% Finance
GSGoldman Sachs $478.21-0.14% 2.1M$158.0B16.7 $28.632.10% Finance
XOMExxon Mobil Corp. $104.56+1.87% 15.9M$438.0B13.4 $7.803.45% Energy
CVXChevron Corp. $152.73-0.91% 7.3M$284.0B14.9 $10.254.12% Energy
10 of 12 stocks·1 left·8 scrollable·1 right·Hover headers to pin, drag handles to resize

Cell Editing

An inventory management grid where editing is the primary workflow. Product name, price, and quantity are editable; invalid values show inline errors and block commit. Every committed edit pushes a { from, to } entry onto a createTimeline, which powers the Undo / Redo buttons and the history log.

File breakdown:

FileRole
data.ts8 products across electronics, accessories, and peripherals
columns.tsAll columns editable + sortable, with validate functions
useEditableGrid.tsEditing state — grid + createTimeline, edit / commit / cancel and undo / redo handlers
EditToolbar.vueInventory stats, edited-count chip, and Undo / Redo / Clear controls
EditHistory.vueThe timeline-backed edit history log
EditableGrid.vueThe editable table — click-to-edit cells with Enter / Escape / Ctrl+Z keyboard handling

Key patterns:

  • editing.edit(row, column) activates a cell for editing — the cell paints bg-primary/10 so the edit target is unmistakable

  • editing.commit(value) validates first — only true from the validator allows the edit through

  • editing.error persists until the value passes validation or the user cancels

  • onEdit callback fires after a successful commit; the example pushes { row, column, from, to } to a createTimeline({ size: 50 })

  • timeline.undo() / timeline.redo() walk the history; the example applies the recovered from (undo) or to (redo) to the row in place

Items8
Inventory value$56342
Low stock2
Product
SKU
Price
Qty
Category
Wireless MouseWM-1001$29.99150Peripherals
Mechanical KeyboardMK-2010$89.9975Peripherals
USB-C HubUH-3022$49.99200Accessories
27" MonitorMN-4005$349.9930Electronics
Webcam HDWC-5011$59.99120Peripherals
Laptop StandLS-6003$39.9990Accessories
Bluetooth SpeakerBS-7019$79.9960Electronics
Noise-Cancelling HeadphonesNC-8042$199.9945Electronics
Click any cell to edit. Enter or clicking away commits, Escape cancels, Ctrl+Z / Ctrl+Y to undo or redo.

Row Spanning

A portfolio holdings grid with two levels of row spanning — account spans every holding under an account, and assetClass spans every holding within an account-and-class pair. Spanned cells double as aggregation rows by showing the account or asset-class subtotal alongside the label.

File breakdown:

FileRole
data.ts11 holdings across 3 accounts (Wealth, Retirement, Trust) and 4 asset classes (Equities, Bonds, Real Estate, Cash)
columns.ts6 columns: account, asset class, ticker, holding, value, change (pinned right)
useSpanningGrid.tsSpanning logic — the rowSpanning callback and account / asset-class aggregation helpers
SpanningGrid.vueThe table — multi-level row spans, subtotals in spanned cells, the pinned Today column

Key patterns:

  • One rowSpanning(item, column) callback resolves both span levels by checking whether the next consecutive row shares the same account (and, for assetClass, the same account-and-class pair)

  • spans.value.get(rowId).get(columnId) returns { rowSpan, hidden } — render <td> only when !hidden, and set :rowspan from rowSpan

  • Spanned cells display aggregate information (account total, asset-class subtotal) so the spanned row carries domain meaning beyond visual grouping

  • Cells with hidden: true are skipped in rendering — the cell above covers them

  • Spans are clamped to remaining visible rows and never cross page boundaries

Portfolio holdings
Accounts3
Positions11
Portfolio$2,230,000
AccountAsset ClassTickerHoldingValueToday
Wealth Account$1,200,000
Equities$720,000
AAPLApple Inc.$180,000 2.4%
MSFTMicrosoft Corp.$245,000 1.8%
NVDANVIDIA Corp.$295,000 5.2%
Bonds$300,000
US-10YTreasury Note 10Y$150,000 -0.3%
GS-AAAGoldman Corp. AAA$150,000 0.1%
Real Estate$180,000
VNQVanguard REIT$180,000 0.9%
Retirement$580,000
Equities$400,000
VOOS&P 500 ETF$230,000 1.4%
VTITotal Market ETF$170,000 1.6%
Cash$180,000
MMFMoney Market Fund$180,000 0.0%
Trust$450,000
Equities$250,000
GOOGLAlphabet Class A$250,000 3.1%
The Account column spans every holding under that account; Asset Class spans every holding within the same account-and-class pair. The grid resolves both spans from a single rowSpanning callback that walks the source order.

Recipes

Column Layout

Columns are onboarded through grid.columns, sized as percentages (0–100), and can be pinned, resized, reordered, and hidden.

ts
const grid = createDataGrid()

grid.columns.onboard([
  { id: 'name', size: 30, pinned: 'left', minSize: 15, maxSize: 50 },
  { id: 'email', size: 40 },
  { id: 'status', size: 30, pinned: 'right' },
])

grid.onboard(rows.map(value => ({ id: value.id, value })))

// Pin regions
grid.layout.pinned.value      // { left: [...], scrollable: [...], right: [...] }

// Resize — delta-based, neighbor absorbs inverse
grid.layout.resize('name', 5)  // name grows 5%, email shrinks 5%

// Reorder by display index
grid.layout.reorder(0, 2)

// Replace all sizes at once
grid.layout.distribute([40, 35, 25])

// Restore initial state
grid.layout.reset()

Column Visibility

Hide and show columns without redistributing the remaining widths — headless, so the consumer rebalances via distribute() or CSS. all surfaces every column (including hidden ones) each carrying a visible flag, which is exactly the shape a column chooser needs.

ts
const grid = createDataGrid()

grid.columns.onboard(columns)
grid.onboard(rows.map(value => ({ id: value.id, value })))

grid.layout.hide('email')          // exclude from the render set
grid.layout.show('email')          // restore it
grid.layout.toggle('email') // flip current visibility

grid.layout.columns.value     // render set — visible columns only
grid.layout.all.value  // every column, each with a `visible` flag

Cell Editing

Click-to-edit with validation. Does not mutate source data — commit fires a callback.

ts
const grid = createDataGrid({
  editing: {
    onEdit: (row, column, value, item) => {
      console.log(`Updated ${column} on row ${row} to ${value}`)
    },
  },
})

grid.columns.register({
  id: 'email',
  editable: true,
  validate: (value, item) => {
    if (typeof value !== 'string' || !value.includes('@')) return 'Invalid email'
    return true
  },
})

grid.onboard(rows.map(value => ({ id: value.id, value })))

grid.editing.edit(1, 'email')     // Activate cell
grid.editing.commit('new@email')  // Validate and save
grid.editing.cancel()             // Discard
grid.editing.active.value         // { row: 1, column: 'email' } | null
grid.editing.error.value          // 'Invalid email' | null
grid.editing.dirty                // Map of uncommitted edits (ShallowReactive, no .value)

Row Ordering

Post-sort row ordering for drag-and-drop reordering. Backed by createSortable, keyed by row id — index-based addressing was dropped because it drifts under reactive churn.

ts
const grid = createDataGrid()

grid.columns.onboard(columns)
grid.onboard(rows.map(value => ({ id: value.id, value })))

grid.rows.move(rowId, 3)       // Move the row with this id to position 3
grid.rows.order.value          // Current id sequence
grid.rows.reset()              // Clear custom ordering

// Ordering resets on sort change by default
// Set preserveRowOrder: true to keep ordering across sorts

Row Spanning

Merge cells vertically using a spanning function.

ts
const grid = createDataGrid({
  rowSpanning: (item, column) => {
    if (column === 'department') return 3  // span 3 rows
    return 1
  },
})

grid.columns.onboard(columns)
grid.onboard(rows.map(value => ({ id: value.id, value })))

// Span map: item ID → column id → { rowSpan, hidden }
grid.spans.value.get(1)?.get('department')
// { rowSpan: 3, hidden: false }  — render with rowspan="3"

grid.spans.value.get(2)?.get('department')
// { rowSpan: 1, hidden: true }   — skip rendering (covered by row above)

Nested Columns

Column definitions support nesting for grouped headers. Layout and data pipeline use leaf columns only.

ts
const grid = createDataGrid()

grid.columns.onboard([
  { id: 'name', title: 'Name', size: 30 },
  {
    id: 'contact',
    title: 'Contact',
    children: [
      { id: 'email', title: 'Email', size: 40 },
      { id: 'phone', title: 'Phone', size: 30 },
    ],
  },
])

grid.onboard(rows.map(value => ({ id: value.id, value })))

// headers: 2D array with colspan/rowspan for <thead> rendering
grid.headers.value
// [[{ id: 'name', rowspan: 2 }, { id: 'contact', colspan: 2 }],
//  [{ id: 'email' }, { id: 'phone' }]]

API Reference

The following API details are for the createDataGrid composable.
Was this page helpful?

Ctrl+/