Design Systems Hot Takes

Search

People's Primitives

7 min read

I tend to take a minimalist approach when it comes to design systems. One of the reasons for this is the empathy I have for maintainers of these systems. The responsibility of supporting a group of creatives while not being overwhelmed by the number of resources they need is a challenge. However, this doesn’t mean I don’t have empathy for the people looking to express themselves. It’s why I am so proud of the Mise en Mode approach; that it allows for infinite expressions in ways other systems would fail when pressed, and fail in different ways.

One major drawback to Mise en Mode is being new. Not just new in terms of being recent, but also in terms of thinking. It’s the same sort of paradigm shift that took responsive design years to change the way of working, and even more related to the need for semantic tokens to support both light and dark expressions. It’s just hard to think differently for many.

So when I say, you—collectively—need primitives but you—yourself—don’t need primitives, it’s just a different way of thinking.

Why you collectively need primitives

The definition of a primitive is:

A tier of token meant to have a permanent value assigned

An example of this might be color-red-500 which a person should reasonably expect the color red to be represented as a value, perhaps in the middle of a set of reds. A clear benefit to primitive tokens is their ease of reference. In other words, it is much easier to say and remember “color red five hundred” than it is to reference the related color channels that represent this specific color of red. This is helpful to communicate the idea across people and teams. People need primitives.

The life of a token

Here’s a question that could split the room, when thinking about switching between light and dark expressions, should the value of color-red-200 change between these expressions?

In other words, do you expect something loosely like this:

[data-theme="light"] {
    --color-red-200: color-mix(in oklch, red, white 20%);
}

[data-theme="dark"] {
    --color-red-200: color-mix(in oklch, red, black 20%);
}

In the example above, the light theme has the 200 value represent a lighter red. While the dark theme has the same 200 value represent a darker red.

If you expect the value of color-red-200 to change in this way, this is fundamentally not a primitive by definition. Primitive tokens are meant to represent permanent values. They are meant to act like constants in the system, just like how you might store an API key and then use it later. The key’s value is too hard to recall, so storing it in something human readable is helpful.

Why we expect them to be permanent is to meet user expectations. Otherwise, the mental model of token color-red-200 needs to switch between the expression of light and dark. In one context 200 would need to mean lighter, and in another 200 would need to mean darker. It’s easy to get these confused when trying to select which one from the set you are supposed to use. Is color-red-700 the light one now, or is it color-red-200? This is the job for semantic tokens which don’t need to know if the value is light or dark. Semantic tokens don’t describe values, they describe intention and how we express those intentions can change. We can choose a different red from the set of reds. But we shouldn’t change what color-red-200 means. The agreement of how the primitives are prepared should be stable and meet expectations for the organization.

This is especially the case when a brand changes from blue to red, as was the case for AirBnB. Assigning color-blue-500 to the logo might have seemed reasonable at the time. But once the new expression was introduced, it certainly doesn’t make sense to now change the assignment to a red shade for color-blue-500. This would break expectations for the people using the system. A semantic token meant to describe where the brand color is shown would be more appropriate, and we can later reassign the value of that from blue to red or any other special expression in the future.

Why you yourself don’t need primitives

At the point I hope I’ve illustrated the reason why tokens like color-red-500 are meant to be permanent and why they can be helpful to us. But the system itself doesn’t need them, especially a truly semantic one.

Let’s compare the following two blocks of CSS:

:root {
    --error-text: #9e2000;
}
:root {
    --error-text: var(--color-red-700);
}

Other than one having a more human readable value, what is the difference between these approaches? Because the primitive is meant to be permanent, and the hard coded value is permanent, there isn’t much difference. You could argue that if we’re going to reuse the --color-red-700 in multiple places, then having a variable is helpful. But think about the act of reusing the variable. How would you reuse it? In a very basic sense, you would copy and paste the primitive in the places that it is meant to be reused. Well, you’d also do the same if you use the hexcode directly. You’d copy and paste it in the places it was meant to be reused.

“But that’s not how I assign these values,” you say. “I don’t select from a list of hexcodes, I select from a list of names.” Precisely! You use your meaty eyes to look for the name in a dropdown somewhere, but there’s nothing keeping that dropdown from being the set of values themselves other than those meaty eyes of yours. This could be a dropdown of binary numbers for color. The result would be the same, but because you can’t read the value, you are uneasy about using the value. It just feels better when it’s readable.

In fact, in a truly semantic system, the amount of times you’d be choosing a primitive should also be very small, maybe even once! This starts going into how to think of semantic naming and further using contextual expressions; a tangent to the current topic. But a good way to tell if some system could be more semantic is the number of times you use a single primitive. If you find yourself using it several times in a system, it’s possible that these assignments could be reduced to a more generic semantic token for them all. As always, my goal is having a small set of overall tokens, and removing the primitive tier completely is certainly less maintenance overall.

You might think this is only viable when you are maintaining a single theme. However, I’ll argue this really hits home when you are supporting multiple themes. When supporting multiple themes, you could have v1, v2, and v3 of these themes. You could have themes for different seasons, different promotions, and different reasons. So instead of revisiting the same file over and over again, you make a new theme as a new file. A new collection of assignments to semantic tokens. The semantic tokens stay the same, but the assignments change.

That’s what really changes. It’s not the tokens that change, it’s how you assign them. Semantic tokens are a proxy to the experience, but primitives should never be found there. Primitives should only exist in the exercise of assigning values, if at all! It is there in the act of curating that you might as well just use the raw code since you might never return to this theme. You might just create a whole new one from scratch for the next product need, with wholly new values to create that new expression.

For humans and human-like systems

The reality is that you most likely aren’t going to get rid of your primitive tier, especially in large organizations with lots of ideas being shared. People need to discuss design decisions and agreements between other humans. As system professionals, organization seems like the right thing to do and having these sets keeps our decisions organized. And as AI continues to copilot our work as if it was another human in the room, we need to meet its expectations too. We also need to verify that it’s doing the right thing with our meaty eyes.

But if it’s just a system for you, as many design systems nerds tend to tinker with, maybe consider reducing your tokens and you might wind up over the hill.

Bell curve of hard coding the values versus organizing your primitives

Chip Away