Theming
The components are headless: they ship structure, behavior, and accessibility, and every visual decision stays yours to keep, override, or replace. Styling arrives in opt-in tiers, each importable on its own.
The tiers
| Tier | Import | What it gives you |
|---|---|---|
| Headless components | @hyzer-labs/ui | Structure, behavior, ARIA, and keyboard support. Stable hz-* classes and data-* hooks, plus native element defaults. No appearance opinions. |
| Reset | reset.css | A structural-only reset in @layer hz-reset. No colors, no fonts. |
| Tokens | tokens.css | The --hz-* custom properties: palette, roles, intents, type, spacing,
radius, motion. They are generated from one schema. See Tokens & Overrides. |
| Reference theme | theme | A full, token-driven look for every component, in @layer hz-theme. It is the styled starting point this site runs on. You
can take single components from it via theme/components/*.css. |
| Utilities | utilities.css | Single-property helper classes built from tokens: text-color roles and intents, plus logical margins. Use them for one-off spots. They are opt-in, imported like the theme, and cost you nothing if you skip them. See Utilities. |
| Your overrides | your CSS / hyzer.config.ts | Token overrides in plain CSS, generated sheets from the hyzer CLI, or component-level styling on the hz-* hooks. See Example Themes. |
/* Each line is optional. Every tier works without the ones below it. */
@import '@hyzer-labs/ui/reset.css'; /* structural reset (@layer hz-reset) */
@import '@hyzer-labs/ui/tokens.css'; /* the --hz-* custom properties */
@import '@hyzer-labs/ui/theme'; /* the reference theme (@layer hz-theme) */
@import '@hyzer-labs/ui/utilities.css'; /* opt-in utility classes (unlayered) */
@import './your-overrides.css'; /* unlayered: always wins */How the cascade layers work
Every reference-theme rule lives in the hz-theme cascade layer. Each rule is
wrapped in :where(), so it stays at single-class specificity. Your stylesheet is
unlayered, and unlayered CSS always beats layered CSS. You will never need !important, a copy of the theme's selector, or specificity math to override it.
/* Pinned by both reset.css and theme.css, so import order never matters: */
@layer hz-reset, hz-theme;
/* Your stylesheet is UNLAYERED. Unlayered CSS beats every @layer rule.
So any selector you write overrides the theme with no specificity
fight, even a bare class: */
.my-quiet-button {
background: transparent;
}Every component also takes a class prop, merged onto the same element as its hz-* root class. Class order in the attribute has no effect on the cascade. Your rule
wins because your stylesheet is unlayered and the theme is not.
What the theme paints before you write a class
Importing the reference theme styles a handful of bare elements, with no class of yours involved. That is the whole list:
bodytakes thesurfaceandtextroles, the sans font stack, and the base line height.:focus-visiblegets a two-pixelprimaryoutline at a two-pixel offset.- A bare
<a>takesprimary, and:visitedtakessecondary. Browser blue and purple fail on a dark surface. That is why this rule lives in the theme rather than the reset. <kbd>renders as a key: mono font, muted text on the muted surface, a thin border, and a small radius. Press Escape to see one..sr-onlyhides content visually while leaving it for screen readers. Components emit this class, so the theme has to ship the rule.
Each of these except .sr-only is wrapped in :where(), so it sits at
zero specificity. Any class of yours wins, and any element you style yourself is untouched. .sr-only is unlayered on purpose, so a consumer layer that reorders the cascade cannot
accidentally reveal hidden text.
Where to override what
| Goal | Mechanism |
|---|---|
| Retheme colors, type, spacing, radius everywhere | Override --hz-* tokens in plain CSS or a generated sheet. See Tokens & Overrides. |
| Dark mode, or any named theme | Use the data-theme hook. Override hues at the palette layer and
everything chains through. Dark is one named theme, and you can define as many as you
like. The attribute works on any element, not just <html>. See Section themes. |
| Restyle one component, keep the rest | Write unlayered CSS against its hz-* class and data-* hooks, or use the class prop. See Styling Components. |
| A different look entirely | Skip the reference theme and style the headless hooks from scratch. The tokens still help, but nothing requires them. |
| Verify a palette still meets WCAG | Use the exported contrast utilities and the CLI's report. See Contrast & Accessibility. |
Where a rule lives decides whether it needs :global(). A stylesheet you import
never does, because Svelte only scopes styles inside a component's <style> block. A rule in your own component's <style> block does need it for a class you passed to a library component.
That class lands on markup the component renders, so an unwrapped selector is pruned as
unused. Custom-property hooks need no selector at all: set them on any ancestor and they
inherit through the boundary. Styling Components walks through
all three.