I have a hunch – “semantic variables” aren’t what we think they are.
Are you suitably provoked? Okay, let’s proceed.
The – or at least my – definition of semantic is that as a consumer of a design system, I can confidently and predictably pick a variable. That seems pretty straight forward, and something we should also feel confident building for, but I don’t think this is happening at scale.
Which brand?
If we take the example bg-brand
, a quite common name for a colour variable. Sure it tells me it’s a background and it has a brand color as the fill.
But I challenge this simplistic view:
-
In a multi-brand organisation, which brand is this?
-
In an organisation with multiple options for brand color, which one is it?
-
In a situation where you have different tints or emphases of a brand, which one is it?
For those working in brands where your primary brand colour is unfortunately orange or yellow, you are likely trying to run away from using them because of their challenges with accessibility contrasts. This may mean that you use a secondary (complimentary) brand color, acting as a pseudo primary colour when used in an ordinal system.
In this scenario is primary…secondary? No, it’s still primary, we just don’t use it. Thus forcing a thinking exercise where you convince yourself that secondary is primary.
To those of you working with brand colours that are indeed red, yellow, or any colour typically associated with a component’s sentiment, I wish you luck.
Latinate ordinal numbers
Despite the above title looking like gibberish, that’s the technical term for primary, secondary, tertiary etc. These are also very common naming conventions in the land of variables.
Despite familiarity and commonality, I'm slowly but surely coming round to the idea that background-secondary
is not a semantic variable. Semantic should mean clear intent, but an ordinal system like primary, secondary, and tertiary doesn't actually tell you where they should be used and for what.
The above image was originally posted as a tweet.
Don’t get me wrong, I spent hours and hours playing around with these methods of building color palettes and components whilst working on Simple Design System at Figma. What became tricky during this work was building predictable combinations of colour, which I believe is central to the successful adoption of any system.
I kept banging my head against a wall when trying to compose components, patterns, and screens where I wasn't even sure myself which ordinal to apply in which position; and I made the system!
For those building a design system, a latinate ordinal system is assumed knowledge based on a widely accepted belief that it is the right thing to do. Fantastic systems like Polaris popularised this naming convention, and that’s great! But for those not working in a systems team, are these conventions understood?
In practice
Let’s say you’re building out a screen. The container background is white, and you’d like to add an informational section in a light grey – a common scenario.
As a designer, what is your instinct when applying a colour to this section? Based on familiarity within commonly referenced systems, you’d be forgiven for suggesting something like background-secondary
.
Now, because I spend way too much time thinking about this, I have some questions:
-
Would we choose secondary because we want it to look less intense than the high contrast white?
-
Or secondary because it’s the second most prominent element on the page?
-
Or secondary because of the hierarchy within a section?
This all leaves me with more questions than answers, and suggests a black hole of documentation is ahead.
What baffles me even more here is that in terms of elevation, this secondary element is above the background, which would be safe to presume is in fact primary in this case. It’s secondary, but in reverse order. My head hurts.
Dark mode doesn’t help
In dark mode, the colors become interesting too because as the elements raise in elevation, they typically get lighter. This means that the secondary in reverse problem pops its head into our lives yet again, with my semantic brain exploding at the thought of something named secondary being more prominent than we want.
You could recess the element, making it feel more secondary, yet it’s still standing out like a sore thumb. And…we’re digressing very far from the original point now.
Deepening the problem
I’ve spent a lot of time here talking about the problems of the primary and secondary variable naming conventions, but tertiary also doesn’t help us.
In this example, which I’ve arguably engineered to be confusing, we have a tertiary sidebar element which definitely has less prominence! It works! But secondary still exists as being more prominent against the white.
With tertiary here, it’s the second most prominent main element on a page, but is using a tertiary value. Someone please help.
Just to be clear here, these are deep and perhaps unnecessary questions, but that’s what I’m here for.
Components
Wait, isn’t that little banner inside the main container actually a component and all of this is a useless academic investigation? Probably.
This is essentially what I’m getting at – a semantic decision should be understood and predictable. There’s a challenge with naming where we must strike the balance between too narrow (specific) and too wide (non-specific).
In my opinion, a latinate ordinal number is not semantic because it’s too open for interpretation and therefore too wide. In a similar vein, using a different example, shadow-video-thumbnail
is too narrow, and restricted down to too specific case, compared a more generic, useful, and scalable shadow-card
. Cards are everywhere, thumbnails are perhaps on just a few pages and this smells like a red flag for consistency being deviated in a system.
What I feel here is that because we are introducing ambiguity with an ordinal system, we’re straying far from semantic conventions and back into the deep dark world of primitives.
Are ordinals actually primitives?
Here’s a challenge: is background-default
actually a…primitive? What does background-default
even mean? Let's assume we're working with #fff
here, should we apply that to every single white element? When you put it like that, the answer becomes a very clear "no".
This is specifically the case when you look at a dark mode implementation. White on white may be totally fine in light mode, but we likely want to introduce tonality in dark mode, perhaps a lighter foreground. Our background-default
falls on its face very quickly, because we need nuance across modes.
A more specific background-page
would allow for a fixed white / dark grey across modes here, whilst a background-section
would promote deviation for the light / dark switch.
No variable is an island
In isolation, a variable has no impact. Any colour decision we make is only relevant in the context of other colours. A radius variable must be balanced against its child and parent, so that we can build visually balanced corner radii in our designs. Any line height value is variable when used with different fonts and casing.
Therefore, no variable is an island. They must work together.
It’s at this point where we start to challenge all previous knowns about naming conventions because we need to consider pairs. Does color-text-secondary
only work on color-background-secondary
? That’s unlikely, but when looking at colours with a hue, we may end up there.
When building Simple Design System, I toyed for hours trying to build a complimentary component set for badges and messages. We scrapped this idea in the end for the sake of simplicity, but the token system does still exist, presented below.
The challenge is that you, as a consumer of these variables, have no idea that they are recommended pairs. There is nothing stopping you from applying text-warning-tertiary
onto background-default
and failing accessibility ratio targets of 4:5.1 even though it is a very close 3.97.
Linting
Where I believe this lands us is a world relying on linting to catch system deviations. Linting has become the way to ensure that designs are accurate, accessible, and consistent, but is this happening more often than we need to based on the arguments above?
An unpredictable search experience for consuming variables introduces a point of failure in usage. This trickles down to teams requiring more linting to catch usage errors. The lower our (the designer) confidence in the choices we are making, the higher the requirement for someone else to lint our work.
I can see this leading to a dissatisfied team.
Semantic, but at the component level
I believe that our semantic dreams are still achievable within this ordinal context, but only at the component level.
A primary button is very clearly defined! So is a secondary one! This makes me happy. The values within it though are where we return to the predictable, searchable, and self-documentating variable values from before – color-background-action
, not color-background-primary
.
Just to re-clarify, primary here is ambiguous in that it insinuates the most used element, but in reality is the most prominent. A primary button however, is way clearer in how it should be used.
The problem with default
If you’re building any software in the year 2024, there’s a high likelihood that you have a set of very common patterns – cards, panels, popovers, and toolbars. These often share the same properties, perhaps a background color.
Similar to a previous example, when we introduce branding or system-level dark and light mode switches, we may want to deviate from this notion that they are all using the same values.
This is where we often come across…the default problem.
Again, we’d be forgiven here for following common accepted knowledge that your most used element in a system is called the default option. I prefer the word base, but that’s a different conversation. Anyway, we hit another dark mode wall when we want to use elevation to indicate elevation.
As your elements gets closer to the eyes, you are likely to want to use a lighter color in dark mode. This means that default needs to be split, and we lose our control. Designers at this point have probably detached your variable and gone freestyle.
This is why it’s logical to split these variables up into their dedicated end points or use cases. background-default
doesn’t scale across modes, so introducing background-page
, background-popover
, and background-section
enabled variance within the system, whilst still protecting your goals of cohesion and contrast.
background-card
is more discoverable when you’re making…cards, background-panel
for when making…panels, background-popover
for…you get the idea.
These are likely to share the same values, in fact they almost certainly will, but I don’t think this is a problem. The marginal memory increase in your token file is far outweighed by the efficiency and predictability gains on the consumer side. Hey, it may also reduce the need for so much linting.
It may sound like I’m ringing an alarm bell for component-level variables here, and you know what? Maybe I am on the surface, but this aligns with my proposal for Functional and Presentational tokens from the T+CPS framework pitched over in my other article about atomic design.
A panel
is a presentational token, a tab
is a functional one.
Token pairs
Finally, and very quickly, I'd encourage you to watch this snippet from the Asana talk at Figma's Schema conference. Here, they talk about colour pairs, which I personally really like, and think it could be more widely used.
That was a lot of words. Let me know what you think!