Table Workflows
State persistence
TableState is a plain object, so persisting a view is serializing it. The state entry point adds a versioned envelope and a restore that drops what no longer fits the schema. Where it is stored is the application's choice.
- Fixture
- Benchmark
On this page
Presentation state is worth persisting; data is not. The state describes how the user looks at
the rows: sort rules, filters, the page, hidden columns, their order, widths and pins. Restoring
it later must not break when the schema changed in the meantime, so restoreState() takes the current columns and discards entries that refer to a column
that no longer exists, an operator it does not know, or a value of the wrong type. A malformed or
foreign string yields the default state instead of an exception.
The helpers are pure and measure 796 B gzip as an entry; they do not touch storage. Selection and expansion ids are kept as they are, because only the application knows whether a row id is still valid.
// serializeState(state) → a versioned envelope
{ "version": 1, "state": { "sorting": [], "filters": [], "search": "", "pageIndex": 0, "pageSize": 20,
"selection": [], "expanded": [], "hidden": [], "order": [], "widths": {}, "pinning": {},
"rowPinning": { "top": [], "bottom": [] } } }
// restoreState(text, columns) → a complete TableState
// - unknown version or malformed JSON → defaultState()
// - hidden, order, sorting, filters, widths, pinning: entries for unknown column ids are dropped
// - filters with an unknown operator are dropped; widths must be positive numbers
// - selection and expanded: only string and finite number ids are kept// The storage is yours: localStorage, a URL, a server-side preference.
const params = new URLSearchParams(location.search);
params.set('view', serializeState(state));
history.replaceState(null, '', `?${params}`);Serialize and restore
Serialize presentation state and safely restore only columns that still exist.
Amara Okafor | Active | Engineering | Europe | $4,501 | 20% | 2026-01-07 |
Jonas Lindqvist | Active | Design | Americas | $12,420 | 33% | 2026-06-18 |
Mei Tanaka | Away | Operations | Asia | $20,339 | 46% | 2026-02-01 |
Rafael Duarte | Invited | Growth | Europe | $28,258 | 59% | 2026-07-12 |
Selin Aydın | Active | Engineering | Americas | $36,177 | 72% | 2026-03-23 |
Noah Brenner | Active | Design | Asia | $44,096 | 85% | 2026-08-06 |
{"version":1,"state":{"sorting":[],"filters":[],"search":"","pageIndex":0,"pageSize":6,"selection":[],"expanded":[],"hidden":[],"order":[],"widths":{},"pinning":{},"rowPinning":{"top":[],"bottom":[]}}} <script lang="ts">
import { DataTable, defaultState, type TableState } from '@svelte-lean/table';
import { restoreState, serializeState } from '@svelte-lean/table/state';
import { memberColumns } from '$lib/demo/columns';
import { people } from '$lib/demo/data';
const KEY = 'members-layout';
const data = people(12);
const columns = memberColumns();
let tableState = $state<Partial<TableState>>(defaultState({ pageSize: 6 }));
let notice = $state('');
// Storage is the application's choice: localStorage here, a URL or a server elsewhere.
function save() {
localStorage.setItem(KEY, serializeState(defaultState(tableState)));
notice = 'Layout saved in localStorage.';
}
function restore(withColumns = columns) {
const saved = localStorage.getItem(KEY);
if (!saved) return (notice = 'Nothing saved yet.');
tableState = restoreState(saved, withColumns); // unknown columns are dropped
notice = 'Layout restored.';
}
</script>
<button type="button" data-slean="button" data-variant="outline" onclick={save}>Save</button>
<button type="button" data-slean="button" data-variant="outline" onclick={() => restore()}>Restore</button>
<button type="button" data-slean="button" data-variant="outline" onclick={() => restore(columns.filter((c) => c.id !== 'region'))}>
Restore without the Region column
</button>
<button type="button" data-slean="button" data-variant="outline" onclick={() => (tableState = defaultState({ pageSize: 6 }))}>Reset</button>
<DataTable {data} {columns} getRowId={(row) => row.id} bind:state={tableState} columnControls searchable label="Members" />
<pre>{serializeState(defaultState(tableState))}</pre>
<p role="status">{notice}</p>- The envelope carries a version; a future format change bumps it and old strings restore to defaults.
defaultState(partial)fills a partial state before serializing so every field is present.
API
| Name | Of | Type | Default | Description |
|---|---|---|---|---|
sorting | TableState | Sort[] | [] | Ordered sort rules; the array order is the multi-sort precedence. |
filters | TableState | Filter[] | [] | Column filters with operator and value. |
search | TableState | string | '' | The search text; terms are split on whitespace. |
pageIndex | TableState | number | 0 | Zero-based page. |
pageSize | TableState | number | 20 | Rows per page; 0 shows every row. |
hidden | TableState | string[] | [] | Hidden column ids. |
order | TableState | string[] | [] | Explicit leaf column order. |
widths | TableState | Record<string, number> | {} | Resized widths by column id. |
pinning | TableState | Record<string, Pin> | {} | Pin overrides by column id. |
defaultState | Root entry | (patch?: Partial<TableState>) => TableState | – | A complete state object with optional overrides. |
@svelte-lean/table/state | Entry points | serializeState · restoreState | – | Versioned persistence of presentation state; unknown columns are dropped on restore. |