Powerful data table with native Svelte 5 runes - sorting, filtering, pagination, virtualization, and inline editing
Tags: component data table svelte5 runes
DataTable provides a feature-rich table component built with native Svelte 5 runes. It supports both a simple API for basic use cases and a fully composable API for advanced features like row selection, filtering, virtualization, and inline editing.
Import
<script>
import { DataTable } from '@classic-homes/theme-svelte';
</script>For advanced features, import the sub-components and utilities:
<script>
import {
createDataTable,
DataTableToolbar,
DataTablePagination,
DataTableVirtual,
DataTableEditableCell,
type DataTableColumn,
} from '@classic-homes/theme-svelte';
</script>Basic Usage
The simple API uses the DataTable component directly with data and columns props:
<script>
const data = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
];
const columns = [
{ id: 'name', header: 'Name', accessor: 'name' },
{ id: 'email', header: 'Email', accessor: 'email' },
];
</script>
<DataTable {data} {columns} />Simple API Props
| Prop | Type | Default | Description |
|---|---|---|---|
data |
T[] |
required | Array of data rows |
columns |
DataTableColumn<T>[] |
required | Column definitions |
loading |
boolean |
false |
Show loading state |
loadingRows |
number |
5 |
Number of skeleton rows |
emptyMessage |
string |
'No data available' |
Empty state message |
sortColumn |
string |
- | Current sort column ID (bindable) |
sortDirection |
'asc' | 'desc' |
'asc' |
Sort direction (bindable) |
onSort |
(column: string | null, direction: 'asc' | 'desc') => void |
- | Sort callback (enables server-side sorting); null when cleared |
onRowClick |
(row: T) => void |
- | Row click callback |
caption |
string |
- | Accessible caption |
row |
Snippet<[T, number]> |
- | Custom row renderer |
class |
string |
- | Additional classes |
enablePagination |
boolean |
false |
Enable built-in pagination |
pageSize |
number |
10 |
Rows per page |
getRowId |
(row: T, index: number) => string |
- | Function to get unique row ID |
enableRowSelection |
boolean | ((row: T) => boolean) |
false |
Enable row selection - can be boolean or function for per-row control |
enableMultiRowSelection |
boolean |
true |
Enable multi-row selection (default: true when selection enabled) |
rowSelection |
Set<string> |
$bindable(new Set<string>()) |
Controlled row selection state (Set of row IDs) |
onRowSelectionChange |
(selection: Set<string>) => void |
- | Callback when selection changes |
enableColumnVisibility |
boolean |
false |
Enable column visibility toggling |
columnVisibility |
Set<string> |
$bindable(new Set<string>()) |
Controlled visibility state (Set of hidden column IDs) |
onColumnVisibilityChange |
(hiddenColumns: Set<string>) => void |
- | Callback when visibility changes |
enableFiltering |
boolean |
false |
Enable filtering features |
enableGlobalFilter |
boolean |
false |
Enable global filter input |
globalFilter |
string |
$bindable('') |
Controlled global filter value |
onGlobalFilterChange |
(filter: string) => void |
- | Callback when global filter changes |
columnFilters |
Map<string, string> |
$bindable(new Map<string, string>()) |
Controlled column filters |
onColumnFiltersChange |
(filters: Map<string, string>) => void |
- | Callback when column filters change |
manualFiltering |
boolean |
false |
Use server-side filtering (manual mode) |
pageIndex |
number |
$bindable(0) |
Current page index (0-based) |
onPaginationChange |
(state: PaginationState) => void |
- | Callback when pagination changes |
manualPagination |
boolean |
false |
Use server-side pagination (manual mode) |
pageCount |
number |
- | Total page count (required for server-side pagination) |
enableVirtualization |
boolean |
false |
Enable row virtualization for large datasets (1000+ rows recommended) |
virtualHeight |
string |
'500px' |
Height of the scrolling body when virtualized (CSS value) |
virtualRowHeight |
number |
48 |
Height of each row in pixels for virtualization (default: 48) |
virtualOverscan |
number |
10 |
Number of rows to render outside the visible viewport (default: 10) |
enableEditing |
boolean |
false |
Enable inline cell editing |
onCellEdit |
(rowId: string, columnId: string, value: unknown, row: T) => void |
- | Callback when a cell is edited |
debug |
boolean |
false |
Enable verbose debug logging to console |
Column Type
interface DataTableColumn<T> {
id: string;
header: string;
accessor: keyof T | ((row: T) => unknown);
sortable?: boolean;
width?: string;
align?: 'left' | 'center' | 'right';
format?: (value: unknown, row: T) => string;
filterOptions?: DataTableFilterOption[];
enableEditing?: boolean;
enableHiding?: boolean;
}Sortable Columns
Enable client-side sorting:
<script>
const columns = [
{ id: 'name', header: 'Name', accessor: 'name', sortable: true },
{ id: 'email', header: 'Email', accessor: 'email', sortable: true },
{ id: 'created', header: 'Created', accessor: 'createdAt', sortable: true },
];
</script>
<DataTable {data} {columns} />Custom Cell Formatting
Format cell values:
<script>
const columns = [
{ id: 'name', header: 'Name', accessor: 'name' },
{
id: 'price',
header: 'Price',
accessor: 'price',
align: 'right',
format: (value) => `$${value.toFixed(2)}`,
},
{
id: 'date',
header: 'Created',
accessor: 'createdAt',
format: (value) => new Date(value).toLocaleDateString(),
},
{
id: 'status',
header: 'Status',
accessor: (row) => (row.active ? 'Active' : 'Inactive'),
},
];
</script>
<DataTable {data} {columns} />Built-in Pagination
<DataTable {data} {columns} enablePagination pageSize={10} getRowId={(row) => String(row.id)} />Server-Side Sorting
Handle sorting externally:
<script>
let sortColumn = $state('name');
let sortDirection = $state('asc');
async function handleSort(column, direction) {
sortColumn = column;
sortDirection = direction;
data = await fetchData({ sort: column, order: direction });
}
</script>
<DataTable {data} {columns} {sortColumn} {sortDirection} onSort={handleSort} />Loading State
<DataTable data={[]} {columns} loading loadingRows={10} />Row Click Handler
<DataTable {data} {columns} onRowClick={(row) => goto(`/users/${row.id}`)} />Custom Row Rendering
Full control over row rendering:
<DataTable {data} {columns}>
{#snippet row(item, index)}
<tr class={index % 2 === 0 ? 'bg-muted/30' : ''}>
<td class="px-4 py-3">{item.name}</td>
<td class="px-4 py-3">{item.email}</td>
<td class="px-4 py-3">
<Badge variant={item.active ? 'success' : 'secondary'}>
{item.active ? 'Active' : 'Inactive'}
</Badge>
</td>
<td class="px-4 py-3">
<Button size="sm" variant="ghost" onclick={() => edit(item)}>Edit</Button>
</td>
</tr>
{/snippet}
</DataTable>Advanced API with createDataTable
For complex tables with row selection, filtering, column visibility, and more, use the createDataTable function which provides a reactive table instance using Svelte 5 runes.
See the Advanced DataTable Example for interactive demos of all features.
Creating a Table Instance
<script lang="ts">
import {
createDataTable,
DataTableToolbar,
DataTablePagination,
type DataTableColumn,
} from '@classic-homes/theme-svelte';
interface User {
id: number;
name: string;
email: string;
role: string;
}
// Column definitions
const columns: DataTableColumn<User>[] = [
{ id: 'id', header: 'ID', accessor: 'id', sortable: true },
{ id: 'name', header: 'Name', accessor: 'name', sortable: true },
{ id: 'email', header: 'Email', accessor: 'email', sortable: true },
{
id: 'role',
header: 'Role',
accessor: 'role',
filterOptions: [
{ value: 'Admin', label: 'Admin' },
{ value: 'Editor', label: 'Editor' },
{ value: 'Viewer', label: 'Viewer' },
],
},
];
// Create table instance
const table = createDataTable<User>(() => ({
data: users,
columns,
getRowId: (row) => String(row.id),
}));
</script>
<!-- Toolbar with search and filters -->
<DataTableToolbar {table} />
<!-- Table rendering -->
<table>
<thead>
<tr>
{#each table.columns as column}
<th onclick={() => column.sortable && table.toggleSort(column.id)}>
{column.header}
</th>
{/each}
</tr>
</thead>
<tbody>
{#each table.rows as row}
<tr>
{#each table.columns as column}
<td>{table.getFormattedCellValue(row.original, column)}</td>
{/each}
</tr>
{/each}
</tbody>
</table>
<!-- Pagination -->
<DataTablePagination {table} />Row Selection
Enable checkbox selection for rows:
<script>
let rowSelection = $state<Set<string>>(new Set());
const table = createDataTable<User>(() => ({
data: users,
columns,
getRowId: (row) => String(row.id),
onRowSelectionChange: (selection) => {
rowSelection = selection;
},
}));
</script>
<!-- Render checkbox in header -->
<th>
<Checkbox
checked={table.isAllSelected}
indeterminate={table.isSomeSelected}
onCheckedChange={() => table.toggleAllRows()}
/>
</th>
<!-- Render checkbox in each row -->
{#each table.rows as row}
<td>
<Checkbox checked={row.isSelected} onCheckedChange={() => table.toggleRowSelection(row.id)} />
</td>
{/each}Column Visibility
Allow users to toggle column visibility:
<script>
let columnVisibility = $state<Set<string>>(new Set());
const table = createDataTable<User>(() => ({
data: users,
columns,
getRowId: (row) => String(row.id),
onColumnVisibilityChange: (hidden) => {
columnVisibility = hidden;
},
}));
</script>
<!-- DataTableToolbar includes column visibility dropdown -->
<DataTableToolbar {table} />Global Search
Filter across all columns:
<script>
let globalFilter = $state('');
const table = createDataTable<User>(() => ({
data: users,
columns,
getRowId: (row) => String(row.id),
onGlobalFilterChange: (filter) => {
globalFilter = filter;
},
}));
</script>
<!-- DataTableToolbar includes a search input -->
<DataTableToolbar {table} />Column Filters
Filter individual columns with faceted filtering:
<script>
const columns: DataTableColumn<User>[] = [
{
id: 'role',
header: 'Role',
accessor: 'role',
filterOptions: [
{ value: 'Admin', label: 'Admin' },
{ value: 'Editor', label: 'Editor' },
{ value: 'Viewer', label: 'Viewer' },
],
},
{
id: 'status',
header: 'Status',
accessor: 'status',
filterOptions: [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
],
},
];
const table = createDataTable<User>(() => ({
data: users,
columns,
getRowId: (row) => String(row.id),
}));
</script>
<!-- DataTableToolbar renders filter dropdowns for columns with filterOptions -->
<DataTableToolbar {table} />Virtualization
Render thousands of rows efficiently:
<script>
const table = createDataTable<User>(() => ({
data: largeDataset, // 10,000+ rows
columns,
getRowId: (row) => String(row.id),
manualPagination: true, // Disable pagination for virtualization
}));
</script>
<DataTableVirtual
{table}
rowHeight={48}
overscan={10}
height="500px"
onRowClick={(row) => console.log(row)}
/>Inline Editing
Enable double-click cell editing:
<script>
function handleCellEdit(rowId: string, columnId: string, value: unknown, row: User) {
// Update your data source
const index = users.findIndex((u) => String(u.id) === rowId);
if (index !== -1) {
users[index] = { ...users[index], [columnId]: value };
}
}
const columns: DataTableColumn<User>[] = [
{ id: 'name', header: 'Name', accessor: 'name', enableEditing: true },
{ id: 'email', header: 'Email', accessor: 'email', enableEditing: true },
];
</script>
{#each table.rows as row}
<tr>
{#each table.columns as column}
<td>
<DataTableEditableCell {row} {column} onSave={handleCellEdit} />
</td>
{/each}
</tr>
{/each}Table Instance API
The createDataTable function returns a reactive table instance with the following properties and methods:
State Properties
| Property | Type | Description |
|---|---|---|
columns |
DataTableColumn<T>[] |
Visible columns |
rows |
DataTableRow<T>[] |
Current page rows |
totalRows |
number |
Total row count |
sortColumn |
string | null |
Current sort column |
sortDirection |
'asc' | 'desc' |
Sort direction |
globalFilter |
string |
Global filter value |
pageIndex |
number |
Current page (0-based) |
pageSize |
number |
Rows per page |
totalPages |
number |
Total page count |
selectedCount |
number |
Selected row count |
isAllSelected |
boolean |
All rows selected |
isSomeSelected |
boolean |
Some rows selected |
canPreviousPage |
boolean |
Can go to previous page |
canNextPage |
boolean |
Can go to next page |
Methods
| Method | Description |
|---|---|
toggleSort(columnId) |
Toggle sort on column |
setSort(columnId, direction) |
Set sort column and direction |
setGlobalFilter(filter) |
Set global filter |
setColumnFilter(columnId, value) |
Set column filter |
clearColumnFilters() |
Clear all column filters |
toggleRowSelection(rowId) |
Toggle row selection |
toggleAllRows() |
Toggle all rows selection |
clearSelection() |
Clear all selections |
toggleColumnVisibility(columnId) |
Toggle column visibility |
showAllColumns() |
Show all columns |
setPage(index) |
Go to page |
nextPage() |
Go to next page |
previousPage() |
Go to previous page |
setPageSize(size) |
Set page size |
getCellValue(row, column) |
Get raw cell value |
getFormattedCellValue(row, column) |
Get formatted cell value |
Helper Components
| Component | Description |
|---|---|
DataTableToolbar |
Search input, column visibility dropdown, facet filters |
DataTablePagination |
Page navigation, rows per page selector |
DataTableVirtual |
Virtualized table for large datasets (1000+ rows) |
DataTableEditableCell |
Inline cell editing with Enter/Escape support |
DataTableSearch |
Standalone global search input |
DataTableColumnVisibility |
Standalone column visibility dropdown |
DataTableColumnFilter |
Per-column text filter input |
DataTableFacetedFilter |
Multi-select faceted filter |
State Types
// Row selection - Set of selected row IDs
type RowSelectionState = Set<string>;
// Column visibility - Set of hidden column IDs
type ColumnVisibilityState = Set<string>;
// Column filters - Map of column ID to filter value
type ColumnFiltersState = Map<string, string>;
// Pagination state
interface PaginationState {
pageIndex: number;
pageSize: number;
}Features
- Simple API: Quick setup with
dataandcolumnsprops - Svelte 5 Runes: Native reactivity with $state and $derived
- Sorting: Client-side or server-side
- Row Selection: Single or multi-select with checkboxes
- Column Visibility: Toggle columns on/off
- Filtering: Global search and per-column filters
- Pagination: Built-in pagination controls
- Virtualization: Render 10,000+ rows efficiently
- Inline Editing: Double-click to edit cells
- Loading States: Skeleton rows while loading
Accessibility
- Table caption for screen readers
- ARIA sort attributes on headers
- Keyboard accessible column headers
- Focus indicators
- Screen reader announcements for selection state
Related Components
- Skeleton - Loading placeholders
- Badge - Status indicators
- Button - Row actions
- Checkbox - Row selection
See Also
- Advanced DataTable Example - Interactive demos with all features
- Basic DataTable Example - Simple pagination example