sveltelean
Versionv0.2.0 GitHub

What raw Svelte gives you

Everything. A dialog is a <dialog> in a component; tabs are a list of buttons with a $state for the selection and a keydown handler. Svelte compiles that to efficient code and delegates certain events itself. Svelte Lean does not claim Svelte's event model is slow or that it needs fixing; the packages are built for Svelte 5, SvelteKit and Vite and would make no sense without them.

tabs.svelte
<script lang="ts">
	// A sketch of tabs in raw Svelte. It is not wrong; it is the start of a list:
	// Home and End, RTL, manual activation, disabled triggers, roving tabindex after a
	// cancelled change, nested tabs, tests for each of these in a browser.
	let current = $state('account');
	const tabs = ['account', 'security'];

	function onkeydown(event: KeyboardEvent, index: number) {
		if (event.key === 'ArrowRight') current = tabs[(index + 1) % tabs.length]!;
		if (event.key === 'ArrowLeft') current = tabs[(index - 1 + tabs.length) % tabs.length]!;
	}
</script>

<div role="tablist" aria-label="Settings">
	{#each tabs as tab, index (tab)}
		<button
			type="button"
			role="tab"
			aria-selected={current === tab}
			tabindex={current === tab ? 0 : -1}
			onclick={() => (current = tab)}
			onkeydown={(event) => onkeydown(event, index)}
		>
			{tab}
		</button>
	{/each}
</div>

The sketch is not wrong. It is the start of a list: Home and End, RTL, manual activation, disabled triggers, the roving tabindex after a cancelled change, nested tabs, the relationship attributes, and a browser test for each. Every project that writes the sketch writes the list, or ships without it.

What the package adds

tabs.svelte
<!-- The same widget on the protocol: the behavior is registered once per page and
     tested once for everyone; the state stays in the attributes you see here. -->
<div data-slean="tabs" data-slean-value="account">
	<div role="tablist" aria-label="Settings" data-slean-part="list">
		<button type="button" id="tab-account" role="tab" aria-controls="panel-account"
			aria-selected="true" tabindex="0" data-slean-part="trigger" data-slean-value="account">
			Account
		</button>
		<button type="button" id="tab-security" role="tab" aria-controls="panel-security"
			aria-selected="false" tabindex="-1" data-slean-part="trigger" data-slean-value="security">
			Security
		</button>
	</div>
	<div id="panel-account" role="tabpanel" aria-labelledby="tab-account"
		data-slean-part="panel" data-slean-value="account">…</div>
	<div id="panel-security" role="tabpanel" aria-labelledby="tab-security"
		data-slean-part="panel" data-slean-value="security" hidden>…</div>
</div>
  • A tested behavior contract: the keyboard, focus, disabled, RTL and ARIA rules of the widget, written down and checked in a browser (tabs contract).
  • Accessibility work done once: the roving tabindex, the activation modes, the cancelable change, the validation that reports a missing panel in the browser console with the trigger value concerned.
  • Browser compatibility knowledge for the native primitives: which feature each depends on and what happens without it.
  • A shared runtime instead of a handler per instance: one click and one keydown listener for every tabs root on the page, measured at 1521 B brotli including the kernel.
  • Build-time discovery, so the behavior is added for the markup that uses it.
  • Styles and tokens per primitive, with dark mode, forced colors and reduced motion handled.
  • The same protocol, events and vocabulary across every primitive and the table.
  • Measurements with their method, so the cost of each piece is known before it is used.

Where raw Svelte stays

Application state stays in Svelte. The selected tab stored in the URL, a dialog controlled by domain state, a table sort connected to a server query: these are Tier 3 by design, and the package leaves them to $state and the application. The table's state is one plain object the application binds. Svelte Lean removes interaction abstractions the platform makes unnecessary; it does not remove Svelte from the interaction.

When to write it yourself

When the interaction is unique to the application and no contract fits, or when it is a native element with no requirement beyond the platform's own behavior and no need for the stylesheet. The plain HTML page covers the second case, and the answer there is the same: sometimes you should.