Skip to main content

DataTable

DataTable

Powerful data table with native Svelte 5 runes - sorting, filtering, pagination, virtualization, and inline editing

Tags: component data table svelte5 runes

View Demo

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.

Live Example

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} />

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 data and columns props
  • 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

See Also​

On this page