sveltelean Table
Versionv0.2.0 GitHub

The controls are native inputs, so the browser provides focus, Space to toggle, the checked state and the radio group semantics. Multiple selection renders one checkbox per row and a header checkbox with an indeterminate state; single selection renders radios that share the name <id>-selection, which is why selection="single" needs the id prop. Each row control is labelled with the selectRow message and the row id; the selected count is announced through a polite live region.

selectionScope decides what the header checkbox and selectAll() act on: the rows on the current page, or every filtered row across pages. Rows that fail isRowSelectable render a disabled control and are excluded from both. Ids stay in the state when a row scrolls off the page or is filtered out, so a selection survives navigation and search.

Multiple selection

Single or multiple selection, disabled records and page or filtered select-all scope.

Row selection
Mode
Select-all scope
0 selected
Amara Okafor
Active
Engineering
$4,501
Jonas Lindqvist
Active
Design
$12,420
Mei Tanaka
Away
Operations
$20,339
Rafael Duarte
Invited
Growth
$28,258
Selin Aydın
Active
Engineering
$36,177
Noah Brenner
Active
Design
$44,096
Page 1 of 2 · 12 rows
[]

Selection.svelte
<script lang="ts">
	import { DataTable, defaultState, type TableState } from '@svelte-lean/table';
	import '@svelte-lean/styles/radio-group.css';
	import { memberColumns, pick } from '$lib/demo/columns';
	import { people } from '$lib/demo/data';

	const data = people(12);
	const columns = pick(memberColumns(), ['name', 'status', 'team', 'revenue']);
	let mode = $state<'multiple' | 'single'>('multiple');
	let scope = $state<'page' | 'filtered'>('filtered');
	let tableState = $state<Partial<TableState>>(defaultState({ pageSize: 6 }));
</script>

<fieldset data-slean="radio-group">
	<legend>Mode</legend>
	<label><input type="radio" name="mode" value="multiple" bind:group={mode} /> multiple</label>
	<label><input type="radio" name="mode" value="single" bind:group={mode} /> single</label>
</fieldset>
<fieldset data-slean="radio-group">
	<legend>Select-all scope</legend>
	<label><input type="radio" name="scope" value="page" bind:group={scope} /> page</label>
	<label><input type="radio" name="scope" value="filtered" bind:group={scope} /> filtered</label>
</fieldset>
<button type="button" data-slean="button" data-variant="outline" onclick={() => (tableState = { ...tableState, selection: [] })}>Clear</button>

<!-- id names the radio group in single mode: <id>-selection. Invited members cannot be selected. -->
<DataTable
	id="members"
	{data}
	{columns}
	getRowId={(row) => row.id}
	bind:state={tableState}
	selection={mode}
	selectionScope={scope}
	isRowSelectable={(row) => row.status !== 'Invited'}
	searchable
	label="Members"
/>

<pre>{JSON.stringify(tableState.selection)}</pre>
Mode and scope are switched by two native radio groups (Tier 0 primitives); Clear is the Button primitive writing an empty list into the state. Invited members cannot be selected. With the filtered scope, search for a team and use the header checkbox: every matching row across pages is selected.
  • Switching to single mode keeps the list; the next selection replaces it with one id.
  • The pre element prints state.selection; nothing else about the rows changes.

Keyboard

KeyWhenResult
Tabin the bodyMoves through the row controls in order.
Spaceon a checkboxToggles the row; on the header checkbox, selects or clears the scope.
ArrowUp/ArrowDownon a radioMoves the selection to the previous or next selectable row (native radio group).

API

NameOfTypeDefaultDescription
selectionControlsfalse | 'single' | 'multiple'falseAdds a radio or checkbox column. 'single' needs the id prop for the radio group name.
selectionScopeControls'page' | 'filtered''page'Rows that select-all and the header checkbox consider: the current page or every filtered row.
isRowSelectableControls(row: T) => boolean() => trueDisables the selection control of rows that fail the rule.
hideSelectAllControlsbooleanfalseHides the header checkbox of multiple selection; the header cell keeps the selectionHeader text for assistive technology.
idData and identitystringundefinedid of the root element. With selection: 'single' it also names the row radios (<id>-selection) so they form one native radio group. Authored, never generated.
selectionTableStateRowId[][]Selected row ids.
selectAllTableMessagesstring'Select all eligible rows'Label of the header checkbox.
selectRowTableMessagesstring'Select row'Prefix of each row control label (followed by the row id).
selectedTableMessages(count: number) => string(count) => `${count} selected`The live selection count in the toolbar.
toggleSelectionTable(id: RowId, selected?: boolean) => void–Toggles one row; in 'single' mode the row replaces the selection.
selectAllTable(ids?: readonly RowId[], selected?: boolean) => void–Selects or clears many rows; defaults to selectable and to select unless all are selected.
selectableTableRowId[]–Ids a selection control may act on within selectionScope.
toggleSelectionRoot entry(current: RowId[], eligible: RowId[], select?: boolean) => RowId[]–Adds or removes the eligible ids; without select it selects unless all are selected.