Definition layers within semantic tokens
We can describe objects in different levels of detail, can we apply this thinking to design tokens?
The design system community get really amped up when discussing tokens. Sometimes it feels like questioning someone’s token structure is like questioning their entire belief system.
I get it, someone once (potentially correctly) criticised my overuse of “default” and our relationship hasn’t been the same since.
Have a nice life Alan.
There’s loads of blogs, threads or posts that suggest reducing the amount of tokens you have as the ultimate goal.
Other articles demand you have enough tokens to retain granular control of everything you’re ever going to need to potentially change, seemingly until the end of time.
Well, I think there is space for both, even in the the same system.
Layers of definition
Semantic tokens, put simply, are assigning meaning to a value. A semantic token is describing what it’s used for, not what the value is.
But any description can be generic or specific.
If I ask someone to describe an object on the shelf in my room they may say “a brown book”.
If I ask someone to describe the book they would say “it’s an old copy of Brave New World by Aldous Huxley”.
If I ask a vintage book store to look at it they may say “it’s a first US edition of Brave New World by Aldous Huxley with significant wear and discolouration to the spine”.
How much specificity there is in a description depends on both:
- the level of expertise someone has
- how much the object needs to be described in context
If I paint a picture of my room, the book becomes part of a larger context. The only information I need about this object is that it’s “a brown book”.
If I paint a picture with the book as the central subject, then I really do need to get into every specific detail.
How does that apply to tokens?
I think a lot of us that work in this space try and retain a single level of magnification when structuring tokens. Loosely speaking, things like text, border, icons will have a similar amount of tokens in them and they’ll follow the same conventions.
What if our semantic token layer was more like a vari-focal lens?
What if we can mix the ultra specific with the ultra generic?
Action tokens
The system I currently work on has a semantic action palette which has almost component level fidelity.
By that I mean we have specified a token for every element of an action (background, border, foreground, focus-ring etc) in every state (default, hover, pressed, focus, focus-visible, disabled).
If one brand needs to change the border of a secondary button focus state, no problem — we’ve got a token for that. 140 of them actually — half of you just fainted, the other half said “only 140?”.
These action tokens feed the obvious components, like all variants of Buttons and Links. On top of that they style the interactive parts of Accordions, Tabs, Tooltips etc too. Pretty much anything that uses an html button
or <a>
under the hood. It’s more efficient than component level tokens for us.
Where this approach falls down is when someone just wants “the damn action colour”.
In Figma, because you can’t use a link component inline, or assign colour to a text style, a consuming designer has to find the very specific token that styles the default link, in order to simulate a link within content. In our case this is color-action-foreground-tertiary-default
.
Shut up about default Alan.
We know this is annoying and encourages people to just use the colour picker in a fit of rage.
In other systems I’ve seen a contrasting approach. Actions described in an ultra generic, much simpler way like color-background-action
or color-text-action
which can be used all over the place.
This approach is useful for user comprehension — they are just much easier to find and understand. On top of that, they can reduce the repetition of assigning the same value to multiple states or elements.
For example if your backgrounds and borders, default, hover, active, focus states all use the same colour it can be tedious to create a handful of tokens where one could be used.
However, to me these tokens have the semantic emphasis in the wrong place. The primary semantic importance is action, not text or background. We’re not applying action semantics to text, we’re styling the foreground or background of a semantic element like a <button>
or <a>
.
I find this approach also leads to components containing a mix of semantic tokens categories that don’t effectively describe how to assemble the UI.
How often have you seen something like this? Where the action text colour can’t actually be combined with the action background colour and another token has to be used in place:
.button {
background-color: var(--color-background-action);
color: var(--color-text-inverse);
}
Can we have it all?
Because we already have the detailed usage tokens I wanted to get some of the benefit of the generic approach too.
Perhaps to help build components in one brand that defy the rules applied to all the others, perhaps to just aid fast and loose choices in unstructured design phases or even to just have a token that indeed was “the damn action colour”.
I’ve tried a couple of different routes.
Global layer
We have a global layer of tokens, well it’s so global it lives just outside the central system as it’s own thing. Perhaps we should call it it the Stratospheric layer… anyway, the tokens recorded in this layer are the absolutely undeniable sources of truth for any consuming library.
This layer does not go into any level of detail or specificity. It contains the pre-system named brand colours and fundamental UI decisions that would be true of any platform. These may include colour of default text and the colour of the default action. It would never include states, because states are a platform level concern.
To serve our needs we could simply expose color_action
token at the highest level to feed both our default (ALAN!) action states and be available to serve the needs of some custom elements directly.
Generic layer
This approach is much like the global layer but the token is treated as both a sibling and a parent of the specific action tokens within the same library.
This might be useful if you have some extra needs for action colours, for example two colours of link that are platform specific — one an accent colour and one say, the colour of body text.
Here I’ve created some generic use action tokens before we get into the detailed ones. No doubt some of you are thinking this is just standard practice for us, but these types of tokens are often for internal use only, and not actually exposed to consumers.
{
"color":{
"action":{
==== GENERIC LAYER ====
"accent":"value",
"subdued":"value",
==== SPECIFIC LAYER ===
"background":{
"primary":{
"default":"{color.action.accent}",
"hover":"value",
"focus":"value",
"pressed":"value",
"disabled":"value"
},
"secondary":{
"default":"value",
"hover":"value",
"focus":"value",
"pressed":"value",
"disabled":"value"
},
"tertiary":{
"default":"value",
"hover":"value",
"focus":"value",
"pressed":"value",
"disabled":"value"
}
},
"border":{
"primary":{
"default":"{color.action.accent}",
"hover":"value",
"focus":"value",
"pressed":"value",
"disabled":"value"
}
etc etc etc etc
}
}
}
}
Both of these approaches give us and our consumers a generic or specific token to use when needed. But what about the other benefit, when most of the states have the same value?
Common suffix
When most of the states have the same value it can feel tedious and unnecessary to include them all.
Hey we’ve all used “default” — Alan for the last time… for more than just the default state before right?
.button, .button:hover, .button:focus {
background-color: var(--action-background-primary-default);
}
Well it’s just a bit of a lie isn’t it? Token people hate lies!
Just because they use the same value doesn’t mean they should all use the default token, default means default state not any state I want to look like default. I’m getting tired of this Alan.
But the intent here is valid, it removes duplicate CSS declarations that aren’t necessary to produce the same output.
While this adds even more tokens I’ve toyed with specifying some state tokens with an additional common
suffix. It could be used as an alias for the other relevant states — or not.
For example:
{
"color":{
"action":{
"background":{
"primary":{
"common":"value",
"default":"{color.action.background.primary.common}",
"hover":"{color.action.background.primary.common}",
"focus":"{color.action.background.primary.common}",
"pressed":"{color.action.background.primary.common}",
"disabled":"value"
}
}
}
}
}
.button, .button:hover, .button:focus {
background-color: var(--action-background-primary-common);
}
While it does add yet another token, it allows the consuming CSS or indeed Figma design to just use one value and just not lie about it. There can be no confusion if someone meant to use default for all states or they just forgot.
If it works, maybe we may consider excluding the specific tokens from the build for certain things. If you need to have the granular access later down the line, then the token structure already exist to make those kind of changes immediately.
What next
Remember the goal here was to consider side by side optional levels of specificity like a vari-focal lens.
Sure, someone may use a more generic token incorrectly because they are not a token expert. But it’s better than using a colour picker or source value.
Someone may use a generic token because they are effectively painting a picture of the room, the element doesn’t require that level of specificity for their use case.
Well, what I actually expect next is about 10,000 comments on this article.
Token people like commenting.
I have only blocked Alan.