Platform
← Foundations

FOUNDATIONS FEATURE

Token-driven UI: skins as data

Visual identity is data, not code. Every primitive reads the active skin's --ui-* tokens, so the same templates render in any palette with zero component CSS.

Live demo

Every swatch reads a real var(--ui-*) token from the active skin — no colour literals. Use the Dark-mode toggle in the top bar and watch all of them repaint live as the same token contract flips light ↔ dark.

--ui-surface-page ink: --ui-text-primary
--ui-surface-panel ink: --ui-text-primary
--ui-surface-raised ink: --ui-text-primary
--ui-surface-sunken ink: --ui-text-primary
--ui-accent-brand ink: --ui-text-on-accent
--ui-state-success ink: --ui-text-on-accent
--ui-state-warning ink: --ui-text-on-accent
--ui-state-danger ink: --ui-text-on-accent
--ui-text-primary border: --ui-border-strong
--ui-text-muted border: --ui-border-subtle
success warning danger
41-token contract balanced / glass / brutalist --ui-* design tokens skins:generate / skins:refine Light and dark by tokens

Token-driven UI — skins as data

Platform-UI CSS reads every visual decision — colour, radius, spacing, motion — from CSS custom properties prefixed --ui-*. A primitive never hard-codes a colour or a border-radius; it references a token. Those tokens are defined by the active skin at runtime (/assets/skins/<slug>/tokens.css), chosen by semitexa/theme from the (tenant, domain, locale) tuple and loaded last so it wins the cascade.

The consequence is the whole point of this pillar: visual identity is a data concern, not a code concern. Components stay portable; the palette is guaranteed consistent across light and dark mode because both modes are emitted by the same token contract.

This showcase is the living proof

The page you are reading is rendered by the moss skin. The very same templates, the same primitives, and the same components also serve framework.semitexa.test, where they render in terracotta. Nothing in the markup changes between the two — there is no per-site override, no theme prop threaded through components, no conditional CSS. One site swaps a tokens.css file for another and the entire surface re-skins itself.

That is what "skins as data" means concretely: the difference between this site and the framework site is a 41-token JSON manifest, not a fork of the components.

The token contract

Every skin emits the same 41 design tokens, in both light and dark modes. Missing a token would break any primitive that resolves var(--ui-X), so the contract is total: an algorithm that cannot emit all 41 tokens in both modes is not a valid algorithm. A skin.json manifest looks, in excerpt, like this:

json
{  "slug": "moss",  "algorithm": "balanced",  "seed": "#4f6b3a",  "knobs": { "radius_scale": "default", "shadow_intensity": "default", "motion_speed": "default" },  "modes": {    "light": {      "ui-surface-page":   "oklch(98% 0.01 130)",      "ui-surface-panel":  "oklch(96% 0.02 130)",      "ui-accent-brand":   "oklch(52% 0.10 135)",      "ui-text-body":      "oklch(28% 0.02 130)",      "ui-border-subtle":  "oklch(88% 0.02 130)"    },    "dark": { "…": "… same 41 keys, dark values …" }  }}

Because the contract is fixed, the same sx-surface="panel" resolves to the right background and border in any skin and either mode — the primitive references --ui-surface-panel and --ui-border-subtle, and the active tokens.css decides what those mean today.

Three algorithms

A skin is generated by one of three shipped algorithms (each a SkinAlgorithm implementation registered in SkinAlgorithmRegistry). They differ in character, not in the token contract they must satisfy:

  • balanced — corporate-readable. Soft drop shadows, conservative radii, smooth transitions, WCAG-AA contrast on brand. The default when prose is ambiguous. (This site's moss skin is balanced.) Knobs: radius_scale, shadow_intensity, motion_speed.
  • glass — translucent frosted panels, larger radii, diffuse shadows, emphasised motion. Modern SaaS / mac-style. Knobs: blur_amount, surface_transparency, shadow_softness.
  • brutalist — bold and structural. Zero radius (except pill), hard offset shadows, instant motion, saturated accents. Neo-brutalist / zine. Knobs: shadow_offset, contrast_boost, shadow_color_mode.

Each algorithm is deterministic: given the same SkinParams it always returns the same SkinPalette — no clocks, no randomness, no environment. That determinism is what lets a skin be checked into a project and reproduced byte-for-byte.

Generating and refining skins from the CLI

The generation tooling ships in the sibling semitexa/skins-base package; platform-ui owns the algorithms, the token contract, and the LLM library. Skins are generated into a project's own src/skins/ directory and served via SkinDiscovery in semitexa/theme.

Deterministic generation from a seed colour:

bash
# moss-style: balanced algorithm, mossy green seed, default knobsbin/semitexa skins:generate balanced "#4f6b3a" --name=moss --mode=light --write# tune the character with repeatable --knob flagsbin/semitexa skins:generate balanced "#2f6fed" \  --knob=radius_scale:rounded --knob=shadow_intensity:pronounced --write

Refining an existing skin — structured edits via --set, or fork it under a new slug:

bash
bin/semitexa skins:refine moss --set=shadow_intensity:pronounced --as=moss-deep --write

Introspection costs nothing and emits no CSS:

bash
bin/semitexa skins:generate --describe   # all algorithms + their knob schemas

LLM-assisted generation (optional)

Both skins:generate --prompt="…" and skins:refine <slug> --prompt="…" accept natural-language descriptions and let a model pick an algorithm, seed, and knobs for you. This path is optional — it requires an external LLM key. The deterministic seed-mode path above needs no model at all and is the reliable default; the LLM is a convenience for translating prose like "a calm forest theme with soft corners" into a starting point you then refine. You can preview the model's resolution without emitting any CSS via skins:explain-prompt "<text>".

Why it matters

When the palette lives in code, re-skinning means a refactor and visual identity becomes a permanent maintenance tax. When the palette lives in 41 tokens, re-skinning is swapping a file, light/dark is two value sets of the same contract, and multi-tenant theming is a (tenant, domain, locale) lookup. The components never learn about the brand — they only ever speak --ui-*.

© Edsger W. Dijkstra:"Simplicity is prerequisite for reliability."

GenerateDeterministic from a seed
# moss-style: balanced algorithm, mossy seed, default knobsbin/semitexa skins:generate balanced "#4f6b3a" --name=moss --mode=light --write

How it works

Every skin emits the same 41 design tokens in both light and dark modes, generated by one of three algorithms — balanced (corporate-readable, this site's moss skin), glass (frosted SaaS) and brutalist (hard structural). Generation is deterministic from a seed colour and knobs via skins:generate; skins:refine edits an existing skin via --set or an optional LLM prompt. This very showcase is the proof: the same templates serve terracotta on framework.semitexa.test — one tokens.css swapped for another, no per-site override.

Why it matters

When the palette lives in code, re-skinning is a refactor and identity is a permanent maintenance tax. When it lives in 41 tokens, re-skinning is swapping a file, light/dark is two value sets of one contract, and multi-tenant theming is a (tenant, domain, locale) lookup. Components never learn the brand — they only ever speak --ui-*. The live demo above proves it: every swatch and primitive reads a real --ui-* token, so flipping the Dark-mode toggle repaints the whole grid from one contract — no second stylesheet, no per-component override.