sveltelean
Versionv0.2.0 GitHub Get started

Let the browser do the work.

Native elements first.

Shared listeners, not copies.

Zero bytes where the browser can.

Primitives and a data table for Svelte 5.

Get started See it work

A real screen, built from primitives.

Harbor Studio Team

2 seats free.

Invitations take a seat once they are accepted.

  • All teams
  • Design
  • Engineering
  • Growth
  • Operations
Actions
Amara Okafor
Engineering
Owner
Active
Jonas Lindqvist
Design
Admin
Active
Mei Tanaka
Operations
Member
Away
Rafael Duarte
Growth
Member
Active
Selin Aydın
Engineering
Admin
Active
Noah Brenner
Design
Member
Away
Priya Raman
Operations
Member
Active
Elias Moreau
Growth
Member
Active
Hana Kowalska
Engineering
Admin
Away
Ömer Say
Design
Member
Active
10 members · 2 pending invitations A demo screen: nothing is saved and there is no server.

Invite member

They get an email with a link to join Harbor Studio.

Remove member?

They lose access to every Harbor Studio project.

Most of it weighs nothing.

40 of 60 primitives are plain HTML with a stylesheet. The browser already ships their behavior, so they add no Svelte Lean JavaScript.

Production build, brotli, including the shared kernel each behavior needs. All measurements

Can the browser do it?

One question, asked for every primitive. The answer sets what it costs.

  1. Yes.

    Svelte Lean ships nothing.

    Native elements and attributes: <dialog>, popover, <details>, form controls. The package adds styles and a contract.

  2. Not alone.

    One listener per event, shared.

    A single listener per event type on the document serves every instance. State stays in ARIA attributes.

  3. It needs memory.

    A controller, created on first use.

    Nothing runs until the first interaction. Then a small controller is scoped to that root and cleaned up with it.

Runtime tiers in depth

Same UI. A fraction of the JavaScript.

Measured against Bits UI, the headless library under shadcn-svelte, with the same Svelte, Vite and build. The markup is about as long or longer; the JavaScript is not.

JavaScript shipped

Svelte Lean 0 B
Bits UI 12.4 kB

Source characters, without whitespace

Svelte Lean 330
Bits UI 340

About the same markup. The browser owns the dialog, so there is no JavaScript to ship for it.

Svelte Lean · App.svelte
<button
	type="button"
	commandfor="settings"
	command="show-modal"
>
	Settings
</button>

<dialog
	id="settings"
	aria-labelledby="settings-title"
	aria-describedby="settings-text"
>
	<h2 id="settings-title">Settings</h2>
	<p id="settings-text">Changes apply to this device.</p>
	<button type="button" commandfor="settings" command="close">
		Close
	</button>
</dialog>
Bits UI · App.svelte
<script lang="ts">
	import { Dialog } from 'bits-ui';
</script>

<Dialog.Root>
	<Dialog.Trigger>Settings</Dialog.Trigger>
	<Dialog.Portal>
		<Dialog.Overlay />
		<Dialog.Content>
			<Dialog.Title>Settings</Dialog.Title>
			<Dialog.Description>
				Changes apply to this device.
			</Dialog.Description>
			<Dialog.Close>Close</Dialog.Close>
		</Dialog.Content>
	</Dialog.Portal>
</Dialog.Root>

Bits UI 2.19.3, Svelte 5.57.1, Vite 7.3.6; production builds, brotli, Svelte's own runtime excluded on both sides. Method and cases · Full comparison

A table that stays a table.

Svelte Lean Table renders a real <table>. Search, selection, paging and column controls are props. Turn them on.

Density
0 selected
Amara Okafor
Engineering
Europe
$4,501
2026-01-07
Jonas Lindqvist
Design
Americas
$12,420
2026-06-18
Mei Tanaka
Operations
Asia
$20,339
2026-02-01
Rafael Duarte
Growth
Europe
$28,258
2026-07-12
Selin Aydın
Engineering
Americas
$36,177
2026-03-23
Noah Brenner
Design
Asia
$44,096
2026-08-06
Page 1 of 4 · 24 rows
members.svelte
<DataTable
	{data}
	{columns}
	getRowId={(row) => row.id}
	searchable
	selection="multiple"
	state={{ pageSize: 6 }}
/>
Grouping, editing, pivot, export and more

A thousand tabs. No new listeners.

Behavior is registered once per page, not copied into every instance. Mount more tabs and read the document again.

Tabs roots on this page
100
On tabs elements
…
Listeners on the document

Measured against Bits UI

A thousand tabs mounted into an empty page, median of nine runs.

Time to mount 6.3× faster

Svelte Lean 35.1 ms
Bits UI 220.8 ms

JavaScript heap 21.5× smaller

Svelte Lean 2.3 MB
Bits UI 48.9 MB

Headless Chrome, client-side mount, heap after garbage collection. Open the proof page

Root 1, a panel.
Root 2, a panel.
Root 3, a panel.
Root 4, a panel.
Root 5, a panel.
Root 6, a panel.
Root 7, a panel.
Root 8, a panel.
Root 9, a panel.
Root 10, a panel.
Root 11, a panel.
Root 12, a panel.
Root 13, a panel.
Root 14, a panel.
Root 15, a panel.
Root 16, a panel.
Root 17, a panel.
Root 18, a panel.
Root 19, a panel.
Root 20, a panel.
Root 21, a panel.
Root 22, a panel.
Root 23, a panel.
Root 24, a panel.
Root 25, a panel.
Root 26, a panel.
Root 27, a panel.
Root 28, a panel.
Root 29, a panel.
Root 30, a panel.
Root 31, a panel.
Root 32, a panel.
Root 33, a panel.
Root 34, a panel.
Root 35, a panel.
Root 36, a panel.
Root 37, a panel.
Root 38, a panel.
Root 39, a panel.
Root 40, a panel.
Root 41, a panel.
Root 42, a panel.
Root 43, a panel.
Root 44, a panel.
Root 45, a panel.
Root 46, a panel.
Root 47, a panel.
Root 48, a panel.
Root 49, a panel.
Root 50, a panel.
Root 51, a panel.
Root 52, a panel.
Root 53, a panel.
Root 54, a panel.
Root 55, a panel.
Root 56, a panel.
Root 57, a panel.
Root 58, a panel.
Root 59, a panel.
Root 60, a panel.
Root 61, a panel.
Root 62, a panel.
Root 63, a panel.
Root 64, a panel.
Root 65, a panel.
Root 66, a panel.
Root 67, a panel.
Root 68, a panel.
Root 69, a panel.
Root 70, a panel.
Root 71, a panel.
Root 72, a panel.
Root 73, a panel.
Root 74, a panel.
Root 75, a panel.
Root 76, a panel.
Root 77, a panel.
Root 78, a panel.
Root 79, a panel.
Root 80, a panel.
Root 81, a panel.
Root 82, a panel.
Root 83, a panel.
Root 84, a panel.
Root 85, a panel.
Root 86, a panel.
Root 87, a panel.
Root 88, a panel.
Root 89, a panel.
Root 90, a panel.
Root 91, a panel.
Root 92, a panel.
Root 93, a panel.
Root 94, a panel.
Root 95, a panel.
Root 96, a panel.
Root 97, a panel.
Root 98, a panel.
Root 99, a panel.
Root 100, a panel.

The bracket is the browser. The square is Svelte Lean.

Start with the HTML you already know.

npm install @svelte-lean/primitives