Skip to content
Enric Trillo

OpenAvailable for Outside IR35 & C2C contracts

Ruskel — tokens 0.7.0 · ui 0.7.0

Components are the easy half.

shadcn gives you components — the foundation of a design system — but no decisions. Ruskel is the decisions: one spectrum solved against contrast windows rather than picked, two exposures, and rules that say which value belongs where. I build everything on it, including this page.

One — a band resolves to two values

Colour here has two jobs and they have different physics. A mark is seen — a fill, a dot, a rule — and answers to the 3:1 non-text threshold. A text ring value is read, and answers to AA. Push one hue to the chroma that makes a good mark and it fails as type; pull it back until it reads and it turns to mud as a fill. So each band carries both, at the same hue angle, and neither is ever used in the other’s role.

BandText
Interface590nm — --rsk-text-5906.58:1
Systems520nm — --rsk-text-5206.51:1
Compute470nm — --rsk-text-4706.62:1
Intelligence405nm — --rsk-text-4056.60:1

Ratios are the solver’s own, read out of @ruskel/tokens when this page was built — not transcribed. They are the luminous figures, because that is the ground you are reading on; the same tokens re-solve on paper, which is where this rule bites hardest — see below.

Two — radius encodes role

The test is one question: does it hold something, or is it a thing? Anything that holds — a card, an input, a dialog, a code frame — is near-square. Anything that is — a chip, a tag, a status dot — is a pill. Radius stops being taste and starts being a property of the element, which means it can be got wrong, which means it can be reviewed.

--radius-sm 2px
Structure, small — checkboxes, cells
--radius 4px
Structure — cards, inputs, buttons, frames
--radius-lg 6px
Structure, large — dialogs, plates
--radius-pill 999px
Tokens — chips, tags, dots, switches
A panel holding contentTypeScript
Container as token, token as container
A panel holding contentTypeScript
Container 4px, token pill

The rule is only worth having because it makes the wrong version obvious.

Three — two exposures, solved separately

Editorial is ink on paper, luminous is paper on ink. The obvious approach is one brand ring shared by both, and it measures badly: the shared amber sat at 10.15:1 on ink and 1.71:1 on paper — a strong mark in one exposure and invisible in the other. Each exposure now solves its own ring, maximum chroma inside a contrast window, because a floor alone produced a blinding lime beside a dim indigo.

luminouspaper on ink

The same amber, re-solved.

Hue angle is identical across both. Only the intensity moves, which is what actually changes when pigment goes onto a lit surface.

590nm

editorialink on paper

The same amber, re-solved.

Hue angle is identical across both. Only the intensity moves, which is what actually changes when pigment goes onto a lit surface.

590nm

Both panels are live. The right-hand one carries data-exposure="editorial" — the same switch a consuming app throws — and every value inside it re-resolves.

It is on paper that the two rings stop being a nicety. A mark is solved to be seen against its own ground, so on paper it goes dense and dark enough to register as a fill — and pale, low-contrast type. Here is the same sentence in the 590nm mark and in the 590nm text ring, both on the editorial ground:

Retrieval quality is the whole ballgame in a RAG system.
Mark used as type
Retrieval quality is the whole ballgame in a RAG system.
Text ring

Editorial exposure. The mark measures 2.24:1 as type; the text ring measures 4.61:1. On ink those same two tokens measure 8.81:1 and 6.58:1 — which is why one ring cannot serve both grounds.

Four — success is not a colour

Green-for-good spends a taxonomy colour on a status, and then the taxonomy means two things at once. Health is carried by form instead: hollow is fine, a ring is watch, filled is critical. Only alarm gets a hue, because alarm is the one state that should be able to interrupt you.

Fine
Watch
Critical

Which is also why this site’s inline code is grey and its link underlines are grey: a band colour that appears on every noun has stopped being a taxonomy.

How it is kept honest

The palette is a constraint solve, not a set of picks. A Python solver re-derives every declared token and asserts the contrast windows, the sRGB gamut ceilings, the AA floor for coloured type, and the separation floor across the categorical series. It runs on every push, so a colour that drifts out of its window fails the build rather than shipping and being noticed a year later.