sveltelean
Versionv0.2.0 GitHub

Two models

In a component model, an interaction is a set of components the application imports and composes: a root, a trigger, content. The component owns its state, its handlers and its relationships, and exposes props and events. That model is expressive, familiar, and lets a library manage a widget entirely in JavaScript.

In the Svelte Lean model, an interaction is markup on a protocol. The browser owns what it can own (a dialog, a popover, a disclosure, the form controls); where it cannot, one behavior is registered per page and driven by attributes, with the state in the DOM and one shared listener per event type. Nothing is imported for the interaction, and the markup is the whole representation.

The two models answer different questions. A component API asks what the component should do; the protocol asks what the browser already does and adds only the remainder.

Measured

The same UI written with each library, built by Vite in production mode with the same Svelte version. The library column is every package module except Svelte's own runtime, which both sides ship. The case files and the script are in benchmarks/compare.

UISvelte Lean JS (brotli)Bits UI JS (brotli)Svelte Lean charactersBits UI charactersSvelte Lean linesBits UI lines
Dialog0 B12378 B3303401816
Menu1908 B27114 B4143762213
Tabs1488 B5437 B8363395413

A thousand tabs mounted into an empty page in headless Chrome, from before mount() to the second animation frame, and the JavaScript heap after a forced garbage collection:

LibraryMount, medianRangeHeap, median
Svelte Lean35.1 ms33.6 ms to 36.2 ms2.3 MB
Bits UI220.8 ms209.9 ms to 233.6 ms48.9 MB

Bits UI 2.19.3, Svelte 5.57.1, Vite 7.3.6, chrome 153.0.8010.53, Apple M2, 2026-09-27. Nine fresh pages per mount case.

What the numbers do not say

  • The dialog costs Svelte Lean nothing because the browser ships the behavior. Bits UI also ships behavior the native element does not have, such as configurable focus management and scroll lock, and it works the same in browsers without invoker commands.
  • The markup is not shorter. A dialog is about the same length; a menu is a little longer and tabs are much longer, because roles, ids and relationships are written in the HTML, where Bits UI generates them. Characters exclude whitespace; lines are counted after Prettier at 64 columns.
  • One machine, one browser, client-side mounting. Server rendering and hydration are not part of the mount case. Nothing here measures accessibility; both libraries document their own.

Choosing

  • Choose a component model when your team wants a typed component surface for every interaction, when the widgets you need go beyond what Svelte Lean ships today (its runtime side is four behaviors: tabs, menu, listbox and combobox), or when you want one library to own the whole widget in JavaScript regardless of browser support for native features.
  • Choose Svelte Lean when you want native primitives to work before hydration and without client JavaScript, when you want the markup to be the single representation the server, CSS and assistive technology see, when the listener and bundle invariants matter to your pages, and when you are prepared to write the protocol attributes and author the ids yourself.

Use Bits UI if its component model fits your application better. Svelte Lean does not need to replace every library; it needs to be a sound architectural choice for the developers who value its model, and the trade-offs page lists what that choice costs.

Coexistence

The two can share an application. A native dialog or popover written on the protocol has no runtime and no provider, so it does not conflict with a component-based widget on the same page; the tabs and menu behaviors attach to the document and only respond to roots marked with data-slean. What cannot be shared is a single primitive's implementation: a widget is either a component or protocol markup.