sveltelean Table
Versionv0.2.0 GitHub

At a glance

Thirty-six sample rows in the default renderer: search, selection, column controls and paging are props. Sort a column, select rows, hide a column.

0 selected
Columns
Amara Okafor
Active
Engineering
Europe
$4,501
2026-01-07
Jonas Lindqvist
Active
Design
Americas
$12,420
2026-06-18
Mei Tanaka
Away
Operations
Asia
$20,339
2026-02-01
Rafael Duarte
Invited
Growth
Europe
$28,258
2026-07-12
Selin Aydın
Active
Engineering
Americas
$36,177
2026-03-23
Noah Brenner
Active
Design
Asia
$44,096
2026-08-06
Page 1 of 6 · 36 rows

What Svelte Lean Table is

Most tables in an application need identity, sorting, a search box and a pager. Some need selection and expansion. A few need editing, grouping, ranges and clipboard, or ten thousand rows. Svelte Lean Table serves the first group without carrying the last: the renderer contains the semantic table and the interactions that fit in props, and everything larger is a module that enters a build only when it is imported.

Supporting a feature should not mean shipping the feature. The table-basic consumer fixture builds a page with DataTable and its stylesheet in production mode and asserts, in the module graph, that the grouping, pivot, clipboard, export, server and state modules are absent. The Svelte Lean chunk of that build is 16829 B brotli (18900 B gzip), with Svelte's own runtime recorded separately.

The output is a <table> with the elements and attributes assistive technology expects; no cell is a component instance unless the application supplies a snippet; state is serializable and controlled from the outside; every string is a message; server rendering is tested. The engine underneath, createTable(), is exported for renderers of your own.

members.svelte
<script lang="ts">
	import { DataTable, type Column } from '@svelte-lean/table';
	import '@svelte-lean/table/style.css';

	type Member = { id: number; name: string; revenue: number };
	const data: Member[] = [
		{ id: 1, name: 'Amara Okafor', revenue: 1200 },
		{ id: 2, name: 'Jonas Lindqvist', revenue: 9119 }
	];
	const columns: Column<Member>[] = [
		{ id: 'name', header: 'Member' },
		{ id: 'revenue', header: 'Revenue', align: 'end' }
	];
</script>

<DataTable {data} {columns} getRowId={(row) => row.id} />

Capability levels

One product, four levels. A table does not pay for a level it does not use, and the levels are props and imports, not different components.

LevelAddsHowStatus
TableA native <table> with caption, colgroup, thead, tbody, tfoot, scope and aria-sort; snippets for cells; SSR.<DataTable /> with the three required propsshipped
DataTableSorting, search and filters, selection, pagination, expansion and tree rows, pinning, resizing, visibility and order, sticky header, states, messages.Props; state in one objectshipped
Virtual TableA windowed body for very large row counts.A future opt-in (@svelte-lean/virtual), extracted only when a reusable need is provednot shipped
Data GridCell focus and arrow-key navigation, inline editing, range selection, copy and paste, grouping and pivot, persisted state, server data sources.Props that switch the keyboard model on, plus separate entry pointsshipped, rows not virtualized

Build profiles

Two profiles, read from measurement files. The first is a real consumer build; the second is what a grid adds, per entry, because no fixture builds that combination yet.

Basic DataTable

fixtures/table-basic: DataTable and style.css, Vite 7.3.6, Svelte 5.57.1, production build.

Svelte Lean chunk
16829 B brotli · 18900 B gzip
Svelte runtime chunk
15916 B brotli
Rendered modules
core.js, table.svelte.js, messages.js, editing.js, table/navigation.js, table/data-table.svelte
Not included
grouping.js, pivot.js, clipboard.js, export.js, server.js, state.js
Assertions
46 passed

Data Grid

The renderer plus the entries a grid imports, each measured on its own with esbuild; a consumer bundler de-duplicates the shared code, so the entries are not summed here.

renderer
17886 B brotli · 20031 B gzip
editing
617 B brotli · 707 B gzip
clipboard
817 B brotli · 895 B gzip
grouping
1963 B brotli · 2148 B gzip
state
711 B brotli · 796 B gzip
Not included
virtual rows (no such module exists)

Entry points and measured sizes

Every entry of @svelte-lean/table as measured by yarn size into packages/table/artifacts/size.json. Budgets are enforced in CI and sit above the measured value.

EntryFileContainsgzipbrotliBudget (gzip)
rendererdist/index.jsthe root entry: <DataTable />, createTable(), helpers, messages20031 B17886 B22000 B
headlessdist/core.jsthe ./core entry: pure row model functions, no Svelte2475 B2280 B2700 B
enginedist/table.svelte.jscreateTable() on its own (runes)3494 B3204 B3600 B
groupingdist/grouping.jsgroupBy(), summarize(), aggregate()2148 B1963 B2400 B
pivotdist/pivot.jspivot()526 B470 B600 B
editingdist/editing.jsapplyEdit(), setValue(), parseEdit(), createHistory()707 B617 B800 B
csvdist/export.jstoCsv(), parseDelimited(), downloadText()741 B646 B850 B
clipboarddist/clipboard.jscopyRange(), pasteRange(), rangeBounds()895 B817 B1000 B
serverdist/server.jscreateDataSource()369 B310 B425 B
statedist/state.jsserializeState(), restoreState()796 B711 B900 B
stylesdist/style.cssstyle.css6665 B5704 B7300 B

Each entry bundled independently with esbuild (bundle + minify, ESM); shared code is counted per entry here and de-duplicated by consumer bundlers. Svelte peer runtime excluded. gzip level 9 / brotli quality 11 (node:zlib). Budgets are gzip.

Timings

Rows are not virtualized, so a large table costs what its DOM costs. These are the medians the playground benchmark recorded with every row in the document; they are read from the benchmark file with the environment of that run and compare the table with itself, never with another library. Without a committed run the cells read "not measured".

SubjectMedianp95Method
Mount, one thousand rows283.8 ms342.8 mscomponent init to the second animation frame after onMount, client render of the whole table (ssr=false); median of 5 loads
Sort by revenue, one thousand rows114.1 ms117.7 msheader button click (ascending by revenue) to the second animation frame; median of 5
Mount, ten thousand rows3907.1 ms5984.2 mscomponent init to the second animation frame after onMount, client render of the whole table (ssr=false); median of 5 loads
Sort by revenue, ten thousand rows1928.4 ms2142.4 msheader button click (ascending by revenue) to the second animation frame; median of 5

Environment: chromium 153.0.8010.53, Apple M2, darwin 27.0.0, Node v22.23.3, production build, run 2026-09-27. Method and reproduction on Methodology.

Specimens

The 31 examples of the previous site, in their five groups, each living on the page of its feature. Every one is a live DataTable with its Svelte source.

Essentials

  • Basic table — The smallest useful table: typed data, typed columns and stable row identity.
  • Custom cells — Render product-specific content with typed Svelte snippets while the table keeps layout and semantics.
  • Sorting — Single and multi-column sorting with visible priority and controlled state.
  • Filtering and search — Combine global search with per-column filters and custom operators.
  • Pagination — Client or server pagination with controlled page index and page size.
  • Row selection — Single or multiple selection, disabled records and page or filtered select-all scope.

Layout

  • Density and borders — Switch density, stripes and cell borders without changing the row model.
  • Fixed header — Keep headers visible inside a constrained scrolling surface.
  • Fixed columns — Pin identity and action columns while wide records scroll horizontally.
  • Grouped headers — Describe multi-level headers with nested column definitions.
  • Column visibility — Let users hide fields while protecting required columns.
  • Resize and reorder — Resize with pointer or keyboard and persist an explicit column order.
  • Ellipsis — Constrain long values with optional native title disclosure.
  • Cell spans — Merge adjacent cells with row and column span callbacks.
  • Right-to-left — Mirror pinned columns, navigation and resize behavior with one direction prop.

Rows

  • Expandable rows — Reveal one details region per record and control which rows may expand.
  • Tree data — Render nested records through getSubRows with configurable indentation.
  • Row grouping — Derive group rows and aggregates with the optional grouping entry point.
  • Row reordering — Move rows through a controlled callback without hidden data mutation.
  • Row events — Handle click, double-click, context menu and declarative cell actions.
  • Row animation — Animate application-owned row insertion and removal while respecting reduced motion.

Workflows

  • Editing and history — Validate cell edits, persist them in the application and add undo only when needed.
  • Range and clipboard — Select a rectangular range and translate it to or from tab-delimited spreadsheet data.
  • CSV export — Export visible data with correct quoting and spreadsheet-formula protection.
  • Server data — Control loading, errors, row counts and stale requests without coupling the table to a transport.
  • Persisted state — Serialize presentation state and safely restore only columns that still exist.
  • Loading and empty — Make waiting, retrying and zero-result states part of the component contract.
  • Localization — Replace every built-in message and switch layout direction independently.

Analysis

  • Summaries — Render totals aligned with visible and pinned columns.
  • Pivot — Turn long-form records into a compact cross-tab using an optional pure helper.
  • Performance — Repeatable size and browser measurements with the limits stated beside every number.

Compared with a grid

A grid library starts from the grid: virtualized rows, a cell model, an editing engine, and a keyboard runtime that every table pays for. Svelte Lean Table starts from the table and treats the grid as a level the application reaches by adding props and imports, with the semantics of a native table kept at every level. Where a project needs virtualized rows today, this package does not provide them and says so.

Below the component

createTable() is the engine the renderer is built on: it derives the row model, the visible columns and the header rows from getters over your state and exposes every transition. @svelte-lean/table/core is the pure model without Svelte, for tests and servers. Both are documented under Engine in the API reference.