sveltelean Table
Versionv0.2.0 GitHub

The renderer contains no literal strings. Labels of the search input, the row and header checkboxes, the pager buttons, the editor, the column controls and the row-move buttons, the status lines and the empty text are all keys of TableMessages; three of them are functions because they take a count or a column name. en is the default and tr ships alongside; a partial messages object overrides any subset.

locale is passed to the Intl.Collator that sorts text and to the case folding used by search and filters. Folding also normalizes accents and the Turkish dotted and dotless i, so a search in one form finds the other. Number and date formatting are not part of the table: a column's format function does it with whatever formatter the application uses.

a third message set
import { en, type TableMessages } from '@svelte-lean/table';

// Every key is required in a full set; spread a shipped set to override some.
const de: TableMessages = {
	...en,
	search: 'Zeilen durchsuchen',
	empty: 'Keine passenden Zeilen',
	page: (page, pages, rows) => `Seite ${page} von ${pages} · ${rows} Zeilen`,
	selected: (count) => `${count} ausgewählt`
};

Messages

Replace every built-in message and switch layout direction independently.

Localization
Messages
0 seçili
Amara Okafor
Engineering
Europe
$4,501
Jonas Lindqvist
Design
Americas
$12,420
Mei Tanaka
Operations
Asia
$20,339
Rafael Duarte
Growth
Europe
$28,258
Selin Aydın
Engineering
Americas
$36,177
1 / 3 sayfa · 12 satır

Messages.svelte
<script lang="ts">
	import { DataTable, en, tr, type TableMessages } 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', 'team', 'region', 'revenue']);
	let language = $state<'en' | 'tr'>('tr');
	// A partial override on top of a shipped set: replace one string, keep the rest.
	const messages = $derived<Partial<TableMessages>>(
		language === 'tr' ? tr : { ...en, empty: 'Nobody matches this search' }
	);
</script>

<fieldset data-slean="radio-group">
	<legend>Messages</legend>
	<label><input type="radio" name="language" value="en" bind:group={language} /> en</label>
	<label><input type="radio" name="language" value="tr" bind:group={language} /> tr</label>
</fieldset>

<DataTable
	{data}
	{columns}
	getRowId={(row) => row.id}
	{messages}
	locale={language === 'tr' ? 'tr-TR' : 'en-US'}
	searchable
	selection="multiple"
	state={{ pageSize: 5 }}
	label="Members: localization"
/>
Switch between the shipped tr set with a Turkish locale and en with one key overridden: search for something that matches nothing to see the custom empty text. The pager, the selection labels and the status line follow the set; the row data is the same.

Right-to-left

Mirror pinned columns, navigation and resize behavior with one direction prop.

Right-to-left
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
Priya Raman
Away
Operations
Europe
$4,015
98%
2026-04-17
Elias Moreau
Invited
Growth
Americas
$11,934
30%
2026-09-28
Hana Kowalska
Active
Engineering
Asia
$19,853
43%
2026-05-11
Ömer Say
Active
Design
Europe
$27,772
56%
2026-01-22
İpek Yılmaz
Away
Operations
Americas
$35,691
69%
2026-06-05
Lucas Ferreira
Invited
Growth
Asia
$43,610
82%
2026-02-16

Rtl.svelte
<script lang="ts">
	import { DataTable } from '@svelte-lean/table';
	import { memberColumns } from '$lib/demo/columns';
	import { people } from '$lib/demo/data';

	const data = people(12);
	const columns = memberColumns().map((column) =>
		column.id === 'name' ? { ...column, pin: 'left' as const } : { ...column, width: 200 }
	);
</script>

<!-- Without a dir prop the table follows the nearest dir attribute; with one it overrides it. -->
<div dir="rtl">
	<DataTable {data} {columns} getRowId={(row) => row.id} height="16rem" pagination={false} label="Üyeler" />
</div>

<DataTable dir="rtl" {data} {columns} getRowId={(row) => row.id} label="Üyeler" />
This table has no dir prop: toggle the RTL checkbox and it follows the frame's dir attribute, read at interaction time. The pinned Member column moves to the trailing edge through logical insets, the arrow keys are mirrored, and the resize handle drags in the other direction. A dir prop on the table overrides the document.
  • Without a prop the root carries no dir attribute at all; dir="auto" above it is resolved through :dir(rtl) where supported.
  • Text alignment uses logical values (start, end), so numbers stay at the end in both directions.
  • Pinned columns are placed with inset-inline-start and inset-inline-end.

API

NameOfTypeDefaultDescription
messagesPresentationPartial<TableMessages>enOverrides of the visible and accessible strings; missing keys fall back to en.
localePresentationstringundefinedLocale of the collator used for sorting and of case folding in search and filters.
dirPresentation'ltr' | 'rtl'undefinedText direction of the table. Without it the root carries no dir attribute and keyboard, resize and pinning follow the nearest dir attribute in the document.
enRoot entryTableMessages–English messages, the default of the renderer.
trRoot entryTableMessages–Turkish messages.
filterTableMessages(column: string) => string(column) => `Filter ${column}`Label of a column filter input.
pageTableMessages(page: number, pages: number, rows: number | undefined) => string(page, pages, rows) => `Page ${page} of ${pages}` + (rows === undefined ? '' : ` · ${rows} rows`)The pager status line; rows is undefined when a manual table has no rowCount.
selectedTableMessages(count: number) => string(count) => `${count} selected`The live selection count in the toolbar.
foldCore entry(value, locale?) => string–Case and accent folding used by search and text filters (Turkish dotted and dotless i included).