Getting Started

Quick Start

There are two ways to render a grid. Start with the shorthand props, and move to createDataGrid when you need to own the state.

The Shorthand Form

Pass rows, columns and a row id. Sorting, filtering and column operations are registered for you; adding pageSize adds pagination on top, and floatingFilters draws a filter row under the header.

Hoang Kowalski
hoang.kowalski1@example.com
Design
Manager
$127,691.00
Bruno Nguyen
bruno.nguyen2@example.com
Growth
Support
$105,146.00
Bruno Dubois
bruno.dubois3@example.com
Design
Manager
$86,538.00
Farid Haddad
farid.haddad4@example.com
Growth
Analyst
$76,443.00
Jonas Yilmaz
jonas.yilmaz5@example.com
Core
Support
$129,812.00
Quyen Tanaka
quyen.tanaka6@example.com
Growth
Analyst
$72,011.00
40 rows
1–6 of 40
<script lang="ts">
  import { DataGrid, type ColumnDef } from '@sv5ui/datagrid';

  const columns: ColumnDef<Person>[] = [
    { id: 'name', header: 'Name', sortable: true, filter: 'text', flex: 1 },
    { id: 'email', header: 'Email', sortable: true, filter: 'text', flex: 1.4 },
    { id: 'team', header: 'Team', sortable: true, filter: 'set', width: 120 },
    { id: 'salary', header: 'Salary', sortable: true, filter: 'number',
      align: 'right', width: 120, type: 'currency' }
  ];
</script>

<!-- pageSize turns on pagination; toolbar adds quick filter, chips,
     export menu, column chooser and density toggle -->
<DataGrid
  data={people}
  {columns}
  getRowId={(person) => String(person.id)}
  pageSize={6}
  toolbar
/>

Selection

selection takes the defaults, or an options object. The checkbox column is pinned to the left edge and is not a data column.

Hoang Kowalski
hoang.kowalski1@example.com
Design
Manager
$127,691.00
Bruno Nguyen
bruno.nguyen2@example.com
Growth
Support
$105,146.00
Bruno Dubois
bruno.dubois3@example.com
Design
Manager
$86,538.00
Farid Haddad
farid.haddad4@example.com
Growth
Analyst
$76,443.00
Jonas Yilmaz
jonas.yilmaz5@example.com
Core
Support
$129,812.00
Quyen Tanaka
quyen.tanaka6@example.com
Growth
Analyst
$72,011.00
40 rows
1–6 of 40
<!-- true takes the defaults: multiple selection with a checkbox column -->
<DataGrid data={people} {columns} {getRowId} selection pageSize={6} toolbar />

<!-- or configure it -->
<DataGrid
  data={people}
  {columns}
  {getRowId}
  selection={{ mode: 'single', checkbox: true }}
/>

Virtualization

2,000 rows below, with a DOM that only ever holds the visible window plus a little overscan. Virtualization replaces pagination rather than joining it.

Hoang Kowalski
hoang.kowalski1@example.com
Design
Manager
$127,691.00
Bruno Nguyen
bruno.nguyen2@example.com
Growth
Support
$105,146.00
Bruno Dubois
bruno.dubois3@example.com
Design
Manager
$86,538.00
Farid Haddad
farid.haddad4@example.com
Growth
Analyst
$76,443.00
Jonas Yilmaz
jonas.yilmaz5@example.com
Core
Support
$129,812.00
Quyen Tanaka
quyen.tanaka6@example.com
Growth
Analyst
$72,011.00
Mai Nguyen
mai.nguyen7@example.com
Support
Analyst
$83,226.00
Bruno Novak
bruno.novak8@example.com
Support
Analyst
$71,507.00
Grace Dubois
grace.dubois9@example.com
Support
Manager
$94,212.00
Tuan Ivanov
tuan.ivanov10@example.com
Design
Engineer
$105,520.00
Bruno Andersen
bruno.andersen11@example.com
Growth
Support
$62,389.00
Bruno Yilmaz
bruno.yilmaz12@example.com
Core
Designer
$139,212.00
Grace Costa
grace.costa13@example.com
Platform
Support
$96,734.00
Niko Muller
niko.muller14@example.com
Core
Manager
$76,769.00
Olga Yilmaz
olga.yilmaz15@example.com
Platform
Analyst
$66,068.00
Dara Okafor
dara.okafor16@example.com
Platform
Support
$87,888.00
Quyen Silva
quyen.silva17@example.com
Design
Manager
$121,817.00
Grace Haddad
grace.haddad18@example.com
Support
Designer
$137,633.00
Keiko Novak
keiko.novak19@example.com
Core
Analyst
$125,071.00
Tuan Dubois
tuan.dubois20@example.com
Growth
Engineer
$73,645.00
2,000 rows
<!-- virtual replaces pagination. Give the grid a fixed height through
     class, because the viewport is what decides the visible window. -->
<DataGrid
  data={twoThousandPeople}
  {columns}
  {getRowId}
  virtual
  class="h-[400px]"
/>

<!-- with options -->
<DataGrid
  data={rows}
  {columns}
  {getRowId}
  virtual={{ rowHeight: 36, overscan: 8, columns: true }}
  class="h-[400px]"
/>

Loading, Error and Empty

Three replaceable surfaces. The skeleton fills the grid rather than showing a fixed few rows, and an error wins over both loading and data.

0 rows
<!-- Skeleton rows. The count fills the viewport unless loadingRows says otherwise. -->
<DataGrid data={[]} {columns} {getRowId} loading pageSize={5} />

<!-- Error takes precedence over loading and rows. onRetry renders a Retry action. -->
<DataGrid data={[]} {columns} {getRowId} error="Could not reach the server" onRetry={reload} />

<!-- Empty state text -->
<DataGrid data={[]} {columns} {getRowId} emptyText="No members match this filter" />

DataGrid Props

data, columns and getRowId belong to the shorthand form; grid replaces all three. TypeScript rejects a mix of the two.

PropDefault
data-
columns-
getRowId-
grid-
pageSize-
virtual-
selection-
editing-
toolbarfalse
density'standard'
rowClass-
loadingfalse
loadingRowsfills the viewport
errornull
onRetry-
emptyTextlabels.noData
exportFilename'export.csv'
fullWidthRow-
persistState-
ui-
class-

Owning the Grid

createDataGrid returns the grid as a plain object, so the same instance can be read, driven and passed around. Only the features you list are imported.

0 selected sort: none 40 rows after filtering
Hoang Kowalski
hoang.kowalski1@example.com
Design
Manager
$127,691.00
Bruno Nguyen
bruno.nguyen2@example.com
Growth
Support
$105,146.00
Bruno Dubois
bruno.dubois3@example.com
Design
Manager
$86,538.00
Farid Haddad
farid.haddad4@example.com
Growth
Analyst
$76,443.00
Jonas Yilmaz
jonas.yilmaz5@example.com
Core
Support
$129,812.00
Quyen Tanaka
quyen.tanaka6@example.com
Growth
Analyst
$72,011.00
40 rows
1–6 of 40
<script lang="ts">
  import {
    createDataGrid,
    DataGrid,
    columnOps,
    filtering,
    pagination,
    selection,
    sorting
  } from '@sv5ui/datagrid';

  const grid = createDataGrid<Person>({
    data: people,
    columns,
    getRowId: (person) => String(person.id),
    density: 'compact',
    features: [
      sorting({ nulls: 'last' }),
      filtering(),
      columnOps({ pin: false }),
      selection({ mode: 'multiple' }),
      pagination({ pageSize: 6 })
    ]
  });
</script>

<DataGrid {grid} toolbar />

Reading State Back

Each feature exposes reactive state through its accessor and imperative methods through grid.api. The accessor returns undefined when the feature is not registered, which is why the calls above are optional.

Every badge below is one $derived line over an accessor. Sort a column, tick a row, page: they follow, and nothing subscribes to anything.

sort: none 0 selected page 1 of 4 20 rows in the pipeline
Hoang Kowalski
hoang.kowalski1@example.com
Design
Manager
$127,691.00
Bruno Nguyen
bruno.nguyen2@example.com
Growth
Support
$105,146.00
Bruno Dubois
bruno.dubois3@example.com
Design
Manager
$86,538.00
Farid Haddad
farid.haddad4@example.com
Growth
Analyst
$76,443.00
Jonas Yilmaz
jonas.yilmaz5@example.com
Core
Support
$129,812.00
20 rows
1–5 of 20
import { getSelection, getSorting } from '@sv5ui/datagrid';

// Feature state, reactive because it is built from runes
getSelection(grid)?.selectedIds;
getSorting(grid)?.sort;

// Imperative methods every registered feature merges into grid.api
grid.api.setSort?.([{ columnId: 'name', direction: 'asc' }]);
grid.api.clearSelection?.();
grid.api.exportCsv?.({ filename: 'members.csv' });

// The kernel's own two, always present
const snapshot = grid.api.getState();
grid.api.setState(snapshot);

Events

A typed bus on the instance. This is what a server row model listens to, and what an autosaving grid hooks into.

Sort a column, filter, page, select a row or drag a column edge. Every event the grid emits lands here, newest first.

Hoang Kowalski
hoang.kowalski1@example.com
Design
Manager
$127,691.00
Bruno Nguyen
bruno.nguyen2@example.com
Growth
Support
$105,146.00
Bruno Dubois
bruno.dubois3@example.com
Design
Manager
$86,538.00
Farid Haddad
farid.haddad4@example.com
Growth
Analyst
$76,443.00
Jonas Yilmaz
jonas.yilmaz5@example.com
Core
Support
$129,812.00
20 rows
1–5 of 20
  • No events yet.
<script lang="ts">
  // A typed event bus. Every entry carries its own payload shape.
  let log = $state<{ id: number; name: string; payload: string }[]>([]);
  let id = 0;

  function logEvent(name: string, payload: unknown) {
    id += 1;
    log = [{ id, name, payload: JSON.stringify(payload) }, ...log].slice(0, 5);
  }

  grid.events.on('sortChanged', (payload) => logEvent('sortChanged', payload));
  grid.events.on('filterChanged', (payload) => logEvent('filterChanged', payload));
  grid.events.on('pageChanged', (payload) => logEvent('pageChanged', payload));
  grid.events.on('selectionChanged', (payload) => logEvent('selectionChanged', payload));
  grid.events.on('columnResized', (payload) => logEvent('columnResized', payload));
  grid.events.on('columnMoved', (payload) => logEvent('columnMoved', payload));
  grid.events.on('columnPinned', (payload) => logEvent('columnPinned', payload));
  grid.events.on('columnVisibilityChanged', (p) => logEvent('columnVisibilityChanged', p));

  // The rest of the map: rowsCopied, rowExpanded, rowPinnedChanged,
  // rowMoved, cellEdited, rowEdited.
</script>

<ul>
  {#each log as entry (entry.id)}
    <li><Badge label={entry.name} /> <code>{entry.payload}</code></li>
  {/each}
</ul>

createDataGrid Options

OptionDescription
columnsColumn definitions
dataRows for the client row model
getRowIdStable unique row id
featuresFeature modules to register. Order does not matter
densityRow height and cell padding, through CSS variables
rowModel'server' passes filter, sort and window through untouched
localesLanguages the grid may use, chosen from the page language
localeBCP-47 tag forcing one of them
labelsOverrides on top of the chosen language, any subset
announcerThe same, for what the live region says
rowClassClasses added per rendered row

Which Form to Use

Use the shorthand until you need to read the grid from outside the component, choose which features load, switch language in place, or drive a server row model. Moving between them changes the props, not the columns.

The shorthand registers sorting, filtering and column operations, plus pagination, selection, editing and virtualization when their props are set.

<!-- The two forms are mutually exclusive: pass grid, or pass
     data + columns + getRowId. TypeScript rejects a mix. -->

<!-- Shorthand: the component owns the grid -->
<DataGrid data={people} {columns} {getRowId} pageSize={10} />

<!-- Instance: you own the grid, and everything about it -->
<DataGrid {grid} />