Wrangling a blog with Astro Content Collections
How I set up a typed, validated blog pipeline in Astro: indexing, tags, categories, and the kind of schema errors that actually help instead of yell.
Migrating our UI kit to Figma Variables turned design system work into something closer to programming: scope, inheritance, and three-tier tokens.
We transitioned our core UI kit to use Figma Variables in early Q2, and it broke my brain at first. Not because the tool was hard, but because the work stopped looking like design work. I was no longer picking colors or nudging spacing. I was writing what amounted to a schema: naming things, scoping them, deciding what inherits from what, and staring at a dependency graph that looked suspiciously like code I’d seen engineers curse at.
Somewhere in week two I realized the obvious thing. I wasn’t designing a UI kit anymore. I was designing a small programming language, and the components were just the runtime.
The setup that finally clicked was a strict three-tier structure, and I stole the shape of it from the same people who gave us design tokens in the first place.
Global tokens are the primitives. These are the raw, unopinionated values: color.blue.500, space.4, radius.md. They have no meaning on their own. color.blue.500 is just a blue. It doesn’t care what you use it for.
Alias tokens are the semantic layer. This is where the design system gets opinions: color.brand.primary, color.surface.default, color.text.muted. An alias points at a global, but its name describes a role, not a value.
Component tokens are the overrides, scoped to a single component when it needs to diverge from the aliases: button.primary.background, input.border.focus. They should be rare. If you’re writing a lot of them, your alias layer is probably underbuilt.
On paper it’s tidy. In JSON, the flow from tier to tier looks like this:
{
"color": {
"blue": {
"500": { "value": "#3B82F6" }
},
"brand": {
"primary": { "value": "{color.blue.500}" }
}
},
"button": {
"primary": {
"background": { "value": "{color.brand.primary}" }
}
}
}
The rule that took me longest to internalize is that each tier only points one level down, never across or up. A component token references an alias. An alias references a global. Globals reference nothing. That rule, simple as it sounds, is the difference between a system you can refactor and a pile of color codes with aspirations.
The real shift happened when I had to handle variants: light and dark themes, plus responsive tokens for spacing and typography.
In the old world, I’d have duplicated the file, swapped colors, and called it a theme. In Variables, you don’t duplicate, you declare modes. color.surface.default has one name and two values, one per mode. The component references the alias and gets the right value at runtime, which, in Figma’s case, is “when the user toggles the mode.”
That sounds fine until you realize you’ve just described a function. The alias is a function of the current mode. The component is a function of the alias. Suddenly the system has state, the values depend on context, and if you mess up the scope, everything downstream breaks in ways that look nothing like the original mistake.
I caught myself drawing dependency diagrams. I caught myself thinking about default values and fallbacks. I caught myself saying “the input to this token” out loud, to nobody. This is the point where I admitted, quietly, that yes, I was essentially programming, and no, I did not want to talk about it at dinner.
I won’t pretend the migration was clean. The initial setup took three weeks longer than I’d planned, and the extra time wasn’t spent on design, it was spent on naming, auditing, and untangling cases where the same value meant three different things depending on which screen you looked at.
Some specific things that hurt: every one-off color we’d ever introduced needed a home or a deletion decision. Components that had been built with hardcoded hex values had to be rebuilt, not just reskinned. And I had to write a short internal doc explaining the tier rules to anyone else touching the file, because the structure only makes sense if everyone respects it.
The payoff, though, was worth it. Our design-to-development QA time dropped by roughly half. Engineers were no longer asking “which blue is this one,” because the token name answered that question before it was asked. Theme work that used to be its own project was now a side effect of referencing the right alias. And the system started catching our mistakes for us: miswired tokens surfaced as visual bugs in the library before they ever made it to a real screen.
I used to think my job was drawing interfaces. After this migration, I think my job is closer to architecting the logic those interfaces are drawn from. The screens are outputs. The system is the program.
That reframing changed small things and big things. Small: I stopped naming tokens after what they look like and started naming them after what they do. Big: I started talking with engineers about scoping and inheritance the way I used to talk about alignment and contrast. We were, finally, working in the same mental model.
It also made me humbler about changes. You don’t rename a token casually, for the same reason you don’t rename a variable in a five-year-old codebase casually. Somewhere, something depends on it, and the cost of finding out is higher than the cost of leaving it alone.
If your design system still looks like a style guide, I’d gently suggest it’s one migration away from being something more useful. Figma Variables aren’t a visual tool with extra features, they’re infrastructure, and treating them that way is the difference between a UI kit that drifts and one that compounds.
It does require putting on an engineer’s hat, at least for a while. I was not thrilled about that. I got over it, because the resulting system is the first design system I’ve built that my future self isn’t already planning to replace.