Skip to content

Example Themes

Three example themes, sorted by how much of the reference theme each one keeps. Ocean keeps all of it and only redefines --hz-* tokens. Docs keeps all of it too and layers one class-hook override on top, with no palette of its own. Terminal keeps none of it. Ocean and Terminal are generated from a hyzer.config.ts beside them, and like the base tokens they meet WCAG AA on every graded pairing, in the default theme and in dark. All three, compared:

OceanDocsTerminal
Palette via configyesnone — rides the reference themeyes
Class-hook overridesnoneone (.docs-table)yes
Reference themerequiredlayers over itnot imported
Adds new intentsnonophosphor, amber
Scoped to a classruntime-scoped demo onlyunscoped — no class to add anywhere.hz-theme-terminal

Ocean is one end: it shows how far tokens alone get you. Terminal is the other. It never imports the reference theme, so every rule in it is its own. It also grows the system rather than only recoloring it: two intents the library has never heard of, type-checked and contrast-graded like any built-in. Docs sits between them: the same class-hook contract every component ships is enough to extend the reference theme without forking it. If you only read one file, read Terminal's button.css: that is what "headless" actually buys you.

Ocean: tokens only

Not one class hook. Every difference below comes from redefining --hz-* tokens; the reference theme does the rest of the work, unchanged.

import '@hyzer-labs/ui/tokens.css';
import '@hyzer-labs/ui/theme';            // optional
import '@hyzer-labs/ui/theme/examples/ocean.css';
Par saved Windy Hole 7 OB Casual

Course update

Hole 7 tee pad moves back to the long position this weekend.

Next tee time

Saturday, 8:40, Maple Hill.

What is in the bag?

Three drivers, a midrange, and misplaced optimism.

Course conditions

Fairways dry, greens rolling fast.

Docs: layered on the reference theme

Adds no palette at all. Every rule below resolves through the reference theme’s own role and intent tokens, unchanged. What it layers on top instead: page-rhythm scaffold classes and one class-hook override, imported like any theme sheet and needing no class anywhere.

The full .docs-table lesson lives on Styling Components: the unlayered-beats-layered walkthrough, its demo, and its source. This is, verbatim, the sheet design.hyzer.sh imports for its own chrome. You're reading it right now.

import '@hyzer-labs/ui/tokens.css';
import '@hyzer-labs/ui/theme';                     // layers under it
import '@hyzer-labs/ui/theme/examples/docs/docs.css';
Par saved Windy Hole 7 OB Casual

Course update

Hole 7 tee pad moves back to the long position this weekend.

Next tee time

Saturday, 8:40, Maple Hill.

What is in the bag?

Three drivers, a midrange, and misplaced optimism.

Course conditions

Fairways dry, greens rolling fast.

Terminal: standalone

No reference theme at all. Every rule below is the example’s own, written against the raw headless hooks. The components ship structure, behavior, and ARIA; nothing here inherits a single visual decision from the library.

import '@hyzer-labs/ui/tokens.css';
// NO '@hyzer-labs/ui/theme': that is the point.
import '@hyzer-labs/ui/theme/examples/terminal/terminal.css';
Par saved Windy Hole 7 OB Casual

Course update

Hole 7 tee pad moves back to the long position this weekend.

Next tee time

Saturday, 8:40, Maple Hill.

What is in the bag?

Three drivers, a midrange, and misplaced optimism.

Course conditions

Fairways dry, greens rolling fast.

One instance, styled by hand

Every demo above uses the same markup for the "Go pro" button: <Button class="cta">. The class prop is merged after the component's hz-button root class, so it lands on the element ready for you to style, and sheet-level and instance-level overrides compose. Ocean and Terminal each add one, below. Docs adds none: its "Go pro" button renders exactly as the reference theme paints it, because an unscoped sheet has no class to hang a demo-only rule on without leaking globally.

<Button class="cta">Book a round</Button>

<style>
	/* Rendered as class="hz-button cta". One instance, styled by you.
	   The theme sheet never mentions .cta at all. */
	:global(.hz-theme-terminal .hz-button.cta) {
		box-shadow: 8px 8px 0 var(--hz-intent-danger);
		translate: -2px -2px;
	}
</style>

Note the selector: .hz-theme-terminal .hz-button.cta, not a bare .cta. Terminal is the one example here that roots its overrides at a class. The section below covers why.

Growing the vocabulary

The intents the library ships are a starting set, not a ceiling. A component only stamps data-intent="<name>" and lets the theme decide what that name looks like, so the set of intents belongs to your theme. Terminal adds two: phosphor and amber, the two tubes every real terminal shipped with. They are the last two buttons in its demo above.

Three steps, and none of them is a fork:

  1. Define the token in your config. It is emitted as --hz-intent-amber and graded by the contrast report exactly like primary. A custom intent is held to the same AA bar as a built-in one.
  2. Register the type by augmenting IntentRegistry. Now intent="amber" type-checks and autocompletes on every component, and intent="ambr" is a compile error. See the intents.d.ts tab above.
  3. Style it with one rule, the same shape the built-in intents use.
// hyzer.config.ts: define the token, and it gets contrast-graded
import { defineConfig } from '@hyzer-labs/ui/config';

export default defineConfig({
	tokens: {
		intent: {
			phosphor: 'var(--hz-palette-primary)',
			amber: '#ffb000'
		}
	}
});
/* your theme sheet: one rule per intent, same shape as the built-ins */
.hz-button[data-intent='amber'] {
	--hz-button-accent: var(--hz-intent-amber);
}

Augment the module that declares the interface (@hyzer-labs/ui/types), not the barrel that re-exports it. TypeScript merges an interface only into its declaring module, so declare module '@hyzer-labs/ui' would silently do nothing.

How these work

Why does Terminal use a class instead of :root?

Every rule in Terminal is rooted at a class: .hz-theme-terminal .hz-button rather than .hz-button. Its token block is generated with the engine's selector option instead of :root. So the theme travels with the class: put it on <html> to own a document, or on one element to own a panel. That is why this page can render an Ocean panel and a Terminal panel side by side without them fighting, and why importing this sheet does not disturb the docs site's own theme.

Start from one

Copy a config into your project as hyzer.config.ts, run hyzer generate, and iterate. The contrast report grades every change. To go further than color, copy the theme's components/ folder too and edit the hooks directly; Styling Components is the reference for what each one exposes. The token workflow is documented on Tokens & Overrides.

Coverage: Terminal restyles Button, Badge, Alert, Card, the Field scaffold, TextInput, Toggle, Tabs, and Accordion. It imports no theme, so anything else falls back to bare headless structure. Docs takes the opposite approach: it restyles no bare component hook, so a Table with no .docs-table wrapper is left exactly as the reference theme paints it. Over that theme it adds only scaffold classes, content chrome, and that one opt-in wrapper.