One file decides every colour, size and space on this site — and writes the Figma variables while it's at it. Getting there cost three weeks of renaming things. That turned out to be the part that mattered.
Why custom
The reasonable objection comes first. shadcn/ui exists. So do MUI, Ant Design and Carbon — mature, accessible, maintained by people who are not me. For most products one of those is the right answer, and I would recommend it. A single-brand product with a conventional interface should not be paying someone to hand-build a Select.
That flips when the tokens are what carries the value and the components are just what shows it. Every component library that speeds up your setup also makes decisions you didn't: spacing scales, elevation models, how a button feels when you hover it. Those opinions live in the components, not in your tokens. And when the brand is the variable, you end up spending more time overriding someone else's taste than you'd have spent owning your own.
There is a second reason, and it's the one I didn't see coming. A system you own is a system you can constrain. You decide what things are called, what a component refuses to accept, what fails the build. That felt like a nice-to-have back when the only things reading my code were people.
The cost is real, and I'd rather name it than defend it: no community, no upstream roadmap, and a bus factor of one. I bought back exactly one piece of that — Radix, for the accessibility primitives, and nothing else.
Approach
Three places held the truth and none of them agreed. A value nudged in Figma that never made it to the CSS. A hex edited in code that Figma never heard about. Every fix was manual, and every manual fix was a fresh chance to introduce the next mismatch.
Most token-first systems start in Figma: set the variable, export it, hope the handoff holds. I went the other way round. The JSON file is the source. Style Dictionary compiles it to CSS, and a separate step pushes the same names into Figma. Figma stops being where decisions live and becomes where you check that the pipeline worked. If the two disagree, CSS wins.
One person, no inherited conventions, one rule: every visual decision traces back to a token, no exceptions. Cheap to say. The bill arrived three weeks later, and it wasn't components — it was names.
Design decisions
Three tiers. Primitives hold raw values — a hex, a size, a duration. Semantic tokens hold decisions: color-text-secondary points at a primitive and says what it's for. Component tokens inherit those decisions.
The rule that makes it work is boring and absolute: component CSS never hardcodes a raw value. Everything resolves through a token — usually a semantic one, sometimes a component token pointing straight at a primitive when there's no decision left to name. The build fails the moment a hex or a raw pixel shows up where a token should be.
Which is what makes theming almost free. Dark mode and the bold brand override that middle tier, and everything downstream re-resolves without knowing anything happened. Four combinations. Not one component changed.
Technical decisions
Class names describe structure. Tokens carry everything you can see. Swap one attribute on the html element and every component re-resolves against a different token set — no logic, no JavaScript, no rebuild.
Radix covers the one category I chose not to build myself: components where accessible behaviour is a specification, not a preference. Focus traps, keyboard navigation, ARIA wiring. That is a solved problem, and solving it again would have been vanity. Everything you can see is mine.
Every component carries its own set of stories — 184 across the system — doubling as the documentation a developer would actually read, not just a testing surface. Chromatic checks each one on every pull request; nothing merges without passing, and every build in the project's history is still green.
Where design met code
Open the Figma variable panel next to the Style Dictionary source. Same names, same values — not because anyone synced them, but because there was never anything to sync. Both came from the same file.
The compiled CSS carries that same name straight through to the browser. Three sources, one word, nothing in between to lose in translation. See for yourself:
button-primary-background = #292524button-primary-background: #292524--button-primary-background: #292524Outcome
620 tokens — the same number the build generates, not one typed from memory. Try to hardcode a value instead of using one, and the build won't let you ship it.
Light, dark, and a bolder brand on top of both — four looks from one component tree. Switching is two attributes on the html element. No duplicated components, no theme-specific branches to maintain.
A script compares the live Figma file against the token source every week without me touching it, and opens an issue the moment a single value drifts — the kind of one-character hex difference nobody catches by eye.
Reflection
I added tokens as I needed them. That works fine for a few weeks. Then the system had to carry three layers of naming at once, each with its own logic, and the words I'd used for the first made no sense for the second. color-gray-100 makes a claim about appearance. neutral-100 describes a position. Nothing visual changed. The language changed completely. In a token-first system, naming is architecture — get it wrong and you don't fix the name, you refactor the system.
At the time it felt like three weeks of nothing. Now an agent reviews every branch against the system's rules. A contrast check runs every token pair across all four themes in CI. A weekly job compares Figma against the token source and files whatever drifted. None of it works if two things can go by the same name.
Before agents can coordinate a design system, the design system has to know what its own words mean.
What happened next
Everything above describes a design system built inside a portfolio. It isn't that anymore. I extracted it into its own repository and published it to npm. Extraction is the honest test of whether a system was ever really separable, and it failed the first one: every component still imported from @/, an alias that only resolves inside this site's own build. Nothing broke until the package tried to run somewhere else. Every one of those imports had to become a relative path before it would. The architecture was portable. The code wasn't. Only one of those claims survived contact with a second consumer.
Two studies published in mid-2026 measure whether a design system is usable by AI agents — the DesignSystems.one agent-ready index and Kaelig Deloumeau-Prigent's State of AI in Design Systems. Both score the same way: not for having built something, but for publishing it at a URL anyone can check. I scored 1 out of 5, and 1 out of 10. The foundation was strong — DTCG-format tokens are the rarest signal in the whole index — but nothing was reachable. The governance existed. The system was illegible to anything that hadn't read the source.
So I built the missing layer: a machine-readable markdown twin for every component, generated from the TypeScript source rather than hand-written; a token reference resolving every value across all four theme combinations; a registry, so one CLI command installs a component with its real tokens; a single validate command that gates the build; and an agent skill served from this domain. After: 3 out of 5, and 5 out of 10.
The score is the least interesting part. Building it found three things that had been invisible from the outside. The token reference — the artifact an agent reads to learn what exists — only ever contained colour primitives; eighty-three others, the whole spacing and type scale among them, were missing entirely. A registry item referencing another by name silently resolved against the wrong registry, which only surfaced by running the real CLI against the real URL. And the type check had never completed a single run on this codebase: a config deprecation was swallowing every check behind it, quietly, for months.
The last test was the one I couldn't grade myself. I gave the skill file to an agent with no memory of the project and no access to the source, and asked it to build a page. It found one prop whose type was opaque enough to force a dig into the installed package. I fixed that at the generator, which improved four components rather than one, and ran it again with a second agent on a different task. Zero guessing. Built first try.
The bigger idea
The ambition behind this system, from the start, was bigger than one site: a design system for a media house — one token source governing web products, digital graphics, and motion graphics alike, so a launch video and a landing page are on-brand for the same reason, not because someone checked both against a guideline PDF.
Nothing below this line is built. Worth saying plainly, in a case study that has spent this much effort getting its numbers right: this section is ambition and research, not a shipped feature.
I checked whether someone had already solved it. The clearest answer came from an unlikely place: Frontify commissioned an outside motion studio to build a one-off video toolkit for its own brand. Frontify's product is brand consistency software. If the company selling that problem's solution has to buy a bespoke fix for itself, the product doesn't exist yet.
The reason is mostly structural, not a missing idea: CSS, iOS, and After Effects are different rendering engines, and design tokens only ever solved the ones that accept simple value substitution. What's changed recently is Remotion — it renders video from React and CSS, the same stack this site already runs on, so it doesn't need to translate a token into a foreign format. It just reads the same one.
The full research — the landscape, the evidence, the honest limits — is written up in the repo, not summarised further here: media-system-vision.md.
Most portfolios build something, then document the system behind it. This one is the system. Every visual decision runs through the same token pipeline — the dark and light mode toggle in the navigation is that same system running live, documented step by step in the DS Playbook. The agents that help maintain it work inside the same guardrails: they propose, deterministic checks verify, I decide.
It runs somewhere other than the site describing it now, and the verification is no longer only mine. An agent that had never seen this project built with it and got it right — not because it was clever, but because the system left it nothing to guess. That is what a stable language buys.
Open the inspector. It's all tokens.