Data Table
Batteries-included table for admin list pages — search, filters, sorting, column visibility, pagination, loading skeletons and an empty state in one component. State lives outside the markup: call createDataTable({ rows, columns, rowKey }) (rows/columns must be getter functions so the table tracks them) and hand the returned controller in as table. Any column can be drawn by hand with a snippet named after its id — { id: "status" } is rendered by {#snippet status(row)}.
A full table
Search, filters, sorting, column visibility and paging, from one controller and a list of columns. Any list with those controls uses DataTable — hand-rolling from the raw Table primitives is how the same toolbar, empty row, skeleton and pagination end up copied across three screens. createDataTable is the only thing that talks to TanStack; app code never imports it.
| Channel | Status | |||||
|---|---|---|---|---|---|---|
| 3412 | Ada Lovelace ada@example.com | Web | paid | 3 | $248.90 | 2026-08-09 |
| 3411 | Grace Hopper grace@example.com | shipped | 1 | $89.00 | 2026-08-09 | |
| 3410 | Alan Turing alan@example.com | Web | refunded | 2 | $132.50 | 2026-08-08 |
| 3409 | Katherine Johnson katherine@example.com | paid | 5 | $512.20 | 2026-08-08 | |
| 3408 | Margaret Hamilton margaret@example.com | Web | pending | 1 | $45.00 | 2026-08-07 |
<DataTable
{table}
searchPlaceholder="Search orders…"
empty="No orders found."
pageSizeOptions={[5, 10, 25]}
columnToggle
>
{#snippet status(order)}
<Badge variant="secondary" class="capitalize">{order.status}</Badge>
{/snippet}
{#snippet customer(order)}
<div class="min-w-0">
<p class="truncate text-sm font-medium">{order.customer}</p>
<p class="text-muted truncate text-xs">{order.email}</p>
</div>
{/snippet}
</DataTable>Loading
loading renders skeletonRows placeholder rows, but only while the table has no rows yet — a table that already has data keeps it during a refetch instead of blinking back to bars. A column whose cell has a distinctive shape supplies its own <id>Skeleton snippet, so the placeholder row is the height of the row it is about to become.
| Channel | Status | |||||
|---|---|---|---|---|---|---|
<DataTable table={loading} loading skeletonRows={3} searchPlaceholder="Search orders…">
{#snippet customerSkeleton()}
<div class="min-w-0 flex-1 space-y-1.5">
<Skeleton class="h-4 w-32" />
<Skeleton class="h-3 w-40" />
</div>
{/snippet}
{#snippet statusSkeleton()}
<Skeleton class="h-5 w-16 rounded-full" />
{/snippet}
</DataTable>Empty
Say what is missing, in the table's own body rather than in place of the whole table — the toolbar has to stay reachable, or a reader who filtered themselves into nothing has no way back out.
| Channel | Status | |||||
|---|---|---|---|---|---|---|
| No orders yet. | ||||||
<DataTable table={empty} searchPlaceholder="Search orders…" empty="No orders yet." />API
| Prop | Type | Default | Description |
|---|---|---|---|
table required | DataTableController | Controller returned by | |
rowHref | (row: *) => string | Turns each row into a navigable link to a detail page. | |
onRowClick | (row: *) => void | Click handler for a row, for drawer or modal flows. | |
rowClass | (row: *) => string | Per-row Tailwind classes, e.g. dimming a disabled merchant. | |
loading | boolean | false | Renders |
empty | string | 'No results found.' | Message shown in a full-width centered cell when there are no rows. |
searchPlaceholder | string | Placeholder for the toolbar search input. Omit to hide the search field entirely. | |
skeletonRows | number | 5 | Number of skeleton rows rendered while loading. |
filters | boolean | true | Renders the built-in Filters button whenever at least one column is filterable. Set to |
filterOptions | Object<string, Array<{value: *, label: string}>> | {} |
|
filtersLoading | boolean | false | Replaces the Filters popover body with a spinner while option lists are being fetched. |
onFiltersOpen | () => void | Fires every time the Filters popover opens; the hook for lazy-loading | |
columnToggle | boolean | false | Shows the Columns popover listing every column not marked |
resizable | boolean | false | Lets the user drag column edges; switches the table to |
pageSizeOptions | number[] | Row-count choices offered in the footer, e.g. | |
toolbar | import('svelte').Snippet | Rendered in the left toolbar group, after search and Filters. | |
toolbarEnd | import('svelte').Snippet | Rendered in the right toolbar group, before the column toggle. | |
class | string | Additional Tailwind classes merged onto the root wrapper. | |
cells | Object<string, import('svelte').Snippet<[*]>> | Rest props: snippets named after a column's |
DataTableColumn
| Prop | Type | Default | Description |
|---|---|---|---|
id required | string | Unique column id; also the snippet name for custom cell rendering. | |
accessor | string|false | Dot-path into the row for the cell value; defaults to | |
label required | string | Header text. | |
sortable | boolean | Shows a sortable header button. | |
searchable | boolean | Included in the toolbar search; defaults to true when there's an accessor. | |
filter | boolean|'select'|'text'|'number'|'date'|'boolean' | Forces a filter editor type, or | |
options | Array<{value: *, label: string}> | Explicit value list for a | |
align | 'left'|'center'|'right' | Cell/header text alignment. | |
class | string | Extra classes on each | |
headClass | string | Extra classes on the | |
format | (value: *, row: *) => string | Formats the raw value when no custom cell snippet is provided. | |
hideable | boolean | Set to | |
width | number | Column width in px, used when | |
minWidth | number | Minimum drag width in px. | |
maxWidth | number | Maximum drag width in px. | |
resizable | boolean | Set to | |
hidden | boolean | Initially hidden. |
DataTableQuery
| Prop | Type | Default | Description |
|---|---|---|---|
search required | string | ||
filters required | Object | ||
sort required | {column: string, direction: 'asc'|'desc'}|null | ||
page required | number | ||
perPage required | number |
DataTableController
| Prop | Type | Default | Description |
|---|---|---|---|
rows required | Array<Object> | Visible (paginated/filtered/sorted) rows for the current page. | |
allColumns required | Array<Object> | Every column, including hidden ones (for the Columns toggle). | |
headers required | Array<Object> | Current page's header cells, for the table head row. | |
columns required | Array<Object> | Resolved column defs (for colspan on skeleton/empty rows). | |
filterableColumns required | Array<Object> | Columns eligible for the Filters panel. | |
activeFilters required | Array<{id: string, column: Object, value: *}> | Currently applied filters, for the chips readout. | |
filters required | Object | Draft/applied filter values keyed by column id. | |
dirty required | boolean | True when search/filters/sort differ from initial state (shows the Reset button). | |
pending required | boolean | True when | |
search required | string | Bindable search box value. | |
commits required | boolean | True when the table was created with | |
page required | number | Current 1-based page number (bindable). | |
perPage required | number | Rows per page (bindable). | |
total required | number | Total row count across all pages. | |
totalWidth required | number | Sum of column widths, for the table's fixed-layout width when | |
query required | DataTableQuery | Search/filters/sort/page state, for building a server request. | |
sortOf required | (columnId: string) => ('asc'|'desc'|false) | Current sort direction for a column, if any. | |
toggleSort required | (columnId: string) => void | Cycles a column's sort direction. | |
resizeColumn required | (columnId: string, width: number) => void | Sets a column's width. | |
toggleColumn required | (columnId: string) => void | Shows/hides a column. | |
clearFilter required | (columnId: string) => void | Removes one active filter. | |
counts required | (columnId: string) => Object | Facet value counts for a column's filter editor. | |
reset required | () => void | Clears search, filters and sort back to initial state. | |
apply required | () => void | Commits draft search/filters when |
Also exported from this module: createDataTable