Philosophy
The commitments that underpin this library: accessibility first, headless structure with documented hooks and opt-in theming tiers, all in plain language.
Accessibility first
Each component ships the ARIA roles, keyboard interactions, and focus management of its
WAI-ARIA pattern by default. A Modal traps focus and restores it on close. A Combobox
implements the full editable-combobox pattern with aria-activedescendant. Tabs
handle arrow keys and roving tabindex. You cannot accidentally opt out by restyling.
The same applies to color. The token engine grades every text-on-surface and text-on-intent
pairing against WCAG AA each time it generates a sheet. It warns by default; pass --strict and a failure stops the build. Contrast & Accessibility shows the live ratios.
Headless structure: override with documented hooks or snippets
Two routes get you past a decision the library made.
Documented hooks hand you the styling, and they cover most of what you will
want to change. Every component ships a stable hz-* root class plus data-* and aria-* attributes for every variant and state, listed on
the component's own page. They are API, so styling against them is supported. Every component
also takes a class prop, merged onto its root, so restyling one instance never means
wrapping it in a div.
Snippets are the escape hatch when styling is not enough and you need
different markup. Pass a children or per-item snippet and render your own structure,
without forking the component. The component keeps the parts that are easy to get wrong: roles,
keyboard behavior, focus order.
Theming is opt-in, one tier at a time
On their own, components render with native element defaults. From there you add exactly as much as you want: the token sheet, then the reference theme, then token overrides of your own, then a generated sheet from your own config. Each tier is a superset of the one before. You can stop at any of them.
The example themes are the proof. Ocean redefines only tokens. Terminal imports no reference theme and styles the raw hooks from scratch. Both drive the same components, unchanged.
This site sits in the middle of that range. It runs the reference theme as shipped, plus a small sheet of class overrides that adds no palette and redefines no token. That sheet ships as the Docs example theme, so the styling you are reading is a tier you can import.
No bloat
Components carry structural CSS and nothing else. The reset, token sheet, reference theme and utility classes are separate imports, so you take only the ones you want. Icons come one glyph at a time rather than as a barrel.
The package has exactly one dependency: esm-env, a build-time helper that keeps
the dev-only warnings working in every bundler instead of only Vite. It resolves to constants
your bundler eliminates, so none of it reaches a production build. Svelte is the only peer
dependency.
Shipping less is what makes the headless API and the theming tiers possible: no visual opinion for you to undo, and no layer you are forced to carry. These are the numbers for each layer, measured from the published package rather than estimated:
| What you import | Download (gzipped) | Uncompressed | What that includes |
|---|---|---|---|
| Every component, minified | 52.4 kB | 241 kB | A real build of the whole library, carrying only the few icons the components draw. Import three components and you ship a fraction of this. |
| Component structural CSS | 5.9 kB | 40.1 kB | Layout and behavior only. No colors or other visual styling. |
| Design tokens | 3.2 kB | 12.0 kB | The --hz-* custom properties, also called CSS variables. |
| Reference theme | 50.2 kB | 146 kB | One import for the shared base sheet plus a sheet per component. Import single component sheets instead and you pay less. |
| Reset | 1.3 kB | 2.4 kB | Optional. Structural only. |
| Utilities | 1.7 kB | 6.8 kB | Helper classes you import only if you want them. |
| Everything, together | 115 kB | 448 kB |
Gzipped is what crosses the network. Uncompressed is what the browser holds once it unzips the file.
The first row is the ceiling for JavaScript, not for the total: you will always add at least the token sheet on top of it. Your bundler keeps only the components you import, so three components cost a fraction of that row. The reference theme works the same way. You can import one component sheet at a time instead of the whole thing. The token, reset and utility figures are exact, because you import each of those sheets whole.
1 dependency (build-time only), with svelte as the only peer dependency.
Why the library is written in Svelte
Plain language, for you and your agents
The words a component ships by default (labels, error messages, empty states) are as much a part of its design as the markup. We write short sentences and skip jargon.
Form's error summary is the worked example. Its default title counts the errors: "There is a problem" for one, "There are 3 problems" for three. A screen-reader user gets the count before they start navigating the list.
These docs are indexed in llms.txt, generated from the same manifest that builds the navigation. The Agents page spells out the conventions so a coding agent can follow them literally. Prose that a person can read without decoding is prose an agent can act on without guessing.