sveltelean Table
Versionv0.2.0 GitHub

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.

what is persisted
// 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
a URL as storage
// 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.

Persisted state
Columns
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
Page 1 of 2 · 12 rows
{"version":1,"state":{"sorting":[],"filters":[],"search":"","pageIndex":0,"pageSize":6,"selection":[],"expanded":[],"hidden":[],"order":[],"widths":{},"pinning":{},"rowPinning":{"top":[],"bottom":[]}}}

PersistedState.svelte
<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>
Sort, hide, reorder or resize columns, search, change the page, then Save. Reset and Restore bring the view back from localStorage. Restore without the Region column restores against a schema that lacks that column: its width, order, pin, hidden and sort entries are dropped, the rest survives. The pre element prints the serialized text.
  • 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

NameOfTypeDefaultDescription
sortingTableStateSort[][]Ordered sort rules; the array order is the multi-sort precedence.
filtersTableStateFilter[][]Column filters with operator and value.
searchTableStatestring''The search text; terms are split on whitespace.
pageIndexTableStatenumber0Zero-based page.
pageSizeTableStatenumber20Rows per page; 0 shows every row.
hiddenTableStatestring[][]Hidden column ids.
orderTableStatestring[][]Explicit leaf column order.
widthsTableStateRecord<string, number>{}Resized widths by column id.
pinningTableStateRecord<string, Pin>{}Pin overrides by column id.
defaultStateRoot entry(patch?: Partial<TableState>) => TableState–A complete state object with optional overrides.
@svelte-lean/table/stateEntry pointsserializeState · restoreState–Versioned persistence of presentation state; unknown columns are dropped on restore.