EngineeringMay 27, 20264 min read

White-label tokens from one config file

Colours, radii, spacing and type resolve from one config. How a token pipeline turns a rebrand into a single edit rather than a sweep.

The Orvain UI team

May 27, 2026

White-label tokens from one config file

A rebrand is the test a design system either passes in an afternoon or fails for a quarter. The failing version is familiar: a find-and-replace across a few hundred files, a fortnight of catching the places the replace missed, and a long tail of screens that stayed the old colour because somebody had written the hex value inline eleven months earlier and nobody remembered. The passing version is one file. The difference between them is not effort, it is whether the values were ever allowed to be written down twice.

Three tiers, and references only point one way

A token layer that holds up has three tiers. The primitive tier is the raw material: the full palette, the type scale, the spacing steps, the radii, named for what they are. The semantic tier names roles rather than appearances (surface, raised surface, ink, muted ink, accent, border, danger) and each role points at a primitive. The component tier, where it exists at all, names the handful of decisions a specific component needs and points at semantics.

The rule that makes the tiers worth having is that references only ever point downward, one step at a time. A component that reaches past the semantic tier and grabs a palette value directly is a component that will not follow a rebrand, and it will not announce itself, because it looks completely correct until the day the palette changes underneath everything except it.

Semantic names are the part that survives

Naming by role rather than by appearance sounds like a style guideline and is actually the mechanism. A token called danger keeps meaning the same thing when the brand's error colour moves from red to a deep orange, whereas a token called red becomes a lie that every future reader has to work around. The same argument decides dark mode: a surface that is the lightest thing in the palette and the darkest thing in the palette is still the surface, and only a role-based name can be true in both.

Held to properly, this means the primitive tier can be swapped wholesale. Nothing downstream knows which palette it is reading, because nothing downstream ever named one.

Custom properties do the resolving

CSS custom properties are the right machine for the last step. They are declared at a scope, inherited by everything inside it, and resolved by the browser at use time, so a theme is a set of declarations on a root element and every component that reads a variable follows it without knowing that anything changed. Scope them to a subtree instead of the document and one section of a page can carry a different brand, which is the whole of what a white-label product needs.

The alternative, computing styles in JavaScript at render, puts a style engine in the bundle and a slice of work in front of every render, to arrive at the same declarations the cascade would have produced for free. Orvain ships zero runtime CSS for that reason: the theme resolves in the browser's own resolution step, not in ours.

Rebranding is not only a change of hue

Colour is the visible part and rarely the expensive one. Radii change the character of an interface more than most palettes do, the type scale decides whether a product reads as dense or generous, and the spacing scale is what makes two brands built from identical components feel like different products. A config that only holds colours leaves the other half of the brand hard-coded in the components, which means the second rebrand is as painful as the first.

There is a correctness question hiding here too. Contrast is a property of a pair, not of a colour, so swapping a palette can break the relationship between a surface and the ink that sits on it in a dozen places at once. Pairing those tokens at the semantic tier (every surface carrying the ink that belongs on it) turns that from an audit into a property of the configuration.

What the config has to own to be worth having

For one file to genuinely control the look, it has to hold every value a component is permitted to read, and components have to be unable to read anything else. A config that covers most of the surface is not a weaker version of the same idea; it is a suggestion, and it will be worked around under the first deadline. The payoff for keeping the set closed is that one edit moves the entire library, every block built from it, and every generation an assistant produces against it afterwards.

It helps to remember that the failure mode of a missing token is quiet. A custom property that was never declared resolves to nothing at all, so a mistyped token name produces a component with no background rather than an error, and it gets noticed in a screenshot three weeks later. Generating the variables and the type that names them from the same config closes that gap: a name that does not exist stops being a subtle visual bug and becomes a compile error.

The Orvain UI team

Writing about design systems and the tools that ship them.

All articles

Keep reading. More from the blog.

The Orvain Dispatch

Premium components, free in your inbox.

Join the Orvain Dispatch and get production-ready components, free to keep: React and Vue code plus the Figma frame, yours to ship.

Just components, never spam. You can unsubscribe anytime.