Suzanne Sallaj

Design systems · Stage 1 of 6 · Understand what you are about to build

Introduction to design tokens

By Suzanne Sallaj

· 13 min read · design system series, part 1 of 1

In this post you'll read about three things: what a token is, how to put together the token name, and how the names make a tree and not a list. Three lessons, about an hour, before you open any tool.

If you're a designer, you probably already know these terms: hex code, component, state, and surface. They're mentioned here a lot, so just a brief explanation for others. A hex code is a color written as six letters and numbers, like #17B26A. A component is one piece of a screen, like a button or an alert box. A state is what a button looks like while you hover over it or press it. A surface is the background a thing sits on, light or dark.

Lesson 1 · What a token is

A design token is a decision with a name. A decision is the color. Then that decision is given a name. From then on, every part of the design uses the name instead of the color itself.

How does a name know which color it is? Because somewhere, once, you write the name next to the color:

--color-green-600: #17B26A;

100
200
300
400
500
600#17B26A
700
800
900
1000
The green family that #17B26A belongs to, ten steps from lightest to darkest. --color-green-600 is the sixth step. The number in the name is its place on this strip, not a color of its own.

That line is written one time, in one file. After that, a button never says the hex code. It says the name, and var() means "look up this name and use whatever color it holds":

background: var(--color-green-600);

The green on the screen looks exactly the same as if you had typed #17B26A. What changed is what you can do with it. A hex code does not say what it is for. If someone asks "why is this green?", the hex code cannot answer. A name can. And if you want to change the green later, you change the first line, and every button that uses the name updates.

That is one name on top of a color. The case study below stacks two more names on top of it, each one pointing at the one below, until a button's name says exactly what the button is for.

Case study: breaking down a button

Say you are designing a Save button. It is green, and in your file that green is #17B26A. There are four ways to write that down, and each one is better than the last. Here are all four, with a second button, Publish, added at each level to show what goes wrong or right.

Level 0
the value
SavePublish
background: #17B26Abackground: #17B369Two greens that almost match. Nothing connects them, so nothing catches the difference.
Level 1
a primitive
SavePublish
--color-green-600: #17B26A;background: var(--color-green-600)background: var(--color-green-600)The green is named once, on the first line. Both buttons use the name, so they are the same green. The name says what the color is, not what it is for.
Level 2
semantic
SavePublish
--color-positive: var(--color-green-600);background: var(--color-positive)background: var(--color-positive)A second name, written once, pointing at the green. The buttons use the meaning. Change what --color-positive points to, and every positive thing changes.
Level 3
intent
SaveDefaultSaveHoverSavePressedSaveSelected
--color-action-bg-primary-default: var(--color-positive);background: var(--color-action-bg-primary-default)
Repeat for the following variations.
--color-action-bg-primary-hover: var(--color-positive-strong);background: var(--color-action-bg-primary-hover)
--color-action-bg-primary-pressed: var(--color-positive-stronger);background: var(--color-action-bg-primary-pressed)
--color-action-bg-primary-selected: var(--color-positive-strongest);background: var(--color-action-bg-primary-selected)
A third name per state, each written once and pointing at a meaning. The button uses the name for its state, so nothing is picked by eye.
Figure 0 — The same Save button written four ways. At level 0 a second button drifts. At level 1 it cannot. At level 2 the name carries the meaning. At level 3 the name carries the meaning, the part, and the state.

In summary

Here are the three names stacked up, and the button at the bottom asking for the top one. Each line points at the line above it.

--color-green-600: #17B26A;level 1 · the color, given a name
--color-positive: var(--color-green-600);level 2 · the meaning, pointing at the color
--color-action-bg-primary-default: var(--color-positive);level 3 · the exact job, pointing at the meaning
background: var(--color-action-bg-primary-default);the button · asks for its job name
Figure 0b — Read it from the bottom up. The button asks for its job name. The job name points at the meaning. The meaning points at the green. The green is #17B26A. Change the hex on the first line and every button that ends up there changes with it.
A note on the code. What you just read is how design tokens are written in code. The lines are real, and a developer would type them exactly like that. You will not have to. In the build stages, I show how to get the same result without writing code: you make the decisions, and a tool writes the lines.

What this gives you

"Is this consistent?" stops being a question for a person to check by eye. It becomes a check a program can run. A person needs to look at everything and remember all of it. A program does not. It can check every component, every time the code is saved, and it never gets tired.

Each level takes away one guess. Level 1 takes away "which hex code?". Level 2 takes away "which color means success?". Level 3 takes away "which version of success does this exact element use, in this exact state?". If you skip level 3, that last guess is still there. And that is the guess a program gets wrong.

Lesson 2 · How to put together the token name

A token name has five parts, in a fixed order. Each part answers one question. The first part says what sort of value it is: a color, a space, a radius, a text size. Every name on this page starts with color, so it is easy to forget it is there. The other four parts say whether the thing can be pressed, which part of it this is, what it means, and which version. Read the parts one at a time and the name tells you what the color is for.

  1     2      3  4       5--color-action-bg-primary-hover
  │      │      │    │       └── 5 variant  · which state
  │      │      │    └────────── 4 intent   · what it means
  │      │      └─────────────── 3 property · which part of the element
  │      └────────────────────── 2 nature   · interactive or not
  └───────────────────────────── 1 kind     · what sort of value: a color
Figure 1 — One token dissected. Reads as: "the background color an interactive, primary-importance element uses when the cursor is over it."
SlotOptionsQuestion it answers
kindcolor · space · radius · text · border-widthWhat sort of value is it?
naturestatic · actionCan you click it?
propertylabel · content · border · bg (background)Which part of it?
intentneutral · primary · information · success · warning · dangerWhat does it mean?
variantstates: default hover pressed selected
surfaces: default inverted
Which version?

Where each part is explained on this page. Kind: this page stays on color; the other kinds are their own lesson in stage 3. Nature: the next section, static versus action. Property: the alert figure, four parts of one component. Intent: lesson 3, where intent is the middle column of the tree. Variant: states versus surfaces, under the alert.

Two of the words are easy to mix up. Label means text on the interface, like the words on a button. Content means the main text people read. And the last part of the name depends on the second part. An action token gets a state, because you can press it. A static token gets a surface, because it just sits there, on a light background or a dark one.

Static versus action

The second part of the name splits everything on a screen into two kinds. An action thing is something you can press, like a button. A static thing is something you only look at, like an alert. This one split decides what the last part of the name will be, so here are both kinds with their actual colors attached. One thing to notice along the way: an intent does not promise a color. Primary does not mean blue.

Action. A primary button can be pressed, so its last part is a state: default, hover, pressed, selected. Each state is its own name and its own color.

Save changes
default
Save changes
hover
Save changes
pressed
--color-action-bg-primary-default
#2D5B9F
--color-action-bg-primary-hover
#24497F
--color-action-bg-primary-pressed
#1C3862
--color-action-label-primary-default
#FFFFFF
Figure 2 — Three states, three blues, one name each. The label token is white: same intent, different part.

Static. A danger alert cannot be pressed, so it has no states. Its four parts each get one name, and the last part is a surface instead.

Payment declined
Your card was refused by the issuing bank. Try another card or contact your bank.
--color-static-bg-danger-default
#FEF3F2
--color-static-border-danger-default
#FECDCA
--color-static-label-danger-default
#912018
--color-static-content-danger-default
#B42318
Figure 3 — Four parts, four reds, one family. Nothing here is clickable, so no state ever applies.

This alert is not a surface. It sits on one. A surface is the background behind a thing, and a static thing gets a different set of colors depending on which background it is on. Put this same alert inside a dark sidebar and it would use the inverted names. A state is the other kind of last part, and only things you can press have states.

Card number is not valid
on light · -default
Card number is not valid
on dark · -inverted
Figure 5 — Same sentence, same meaning, same token name up to the final segment. The surface decides which red, and the component never has to know.

Lesson 3 · The names as a tree

Read a name the same way you read a web address: color, then static, then label, then danger, then default. The biggest decision comes first. The smallest comes last. The figure below is the whole tree, every box the rules allow, with one name traced through it in purple. Every box can be clicked, so you can trace any name you like.

Two things to try. First, click a different intent, say warning, or a different property, say border. The purple path moves, the name under the figure rewrites, and the last column stays as it was. The middle parts do not change what the ending can be.

Second, click action. The last column changes from two surfaces to four states. That is the one choice that changes what comes after it. Choose static and the last part has to be a surface, light or dark. Choose action and the last part has to be a state: default, hover, pressed, or selected. Nothing you pick in between can change that, which is why this choice sits so near the top.

Try itClick any box to build a name. Each column is one part of the name. Pick one box per column and watch the name below change.
1 · kind2 · nature (2)3 · property (4)4 · intent (6)5 · variant
the name you built
Figure 6 — The highlighted boxes, read left to right, are one name. Change any box and the name changes with it. Choose action and the last column becomes states.

In summary

The first choice is the big one. When you name a token, you make choices in order: static or action, then which part, then which meaning, then which version. Most choices just add a word. The first one does something extra. If you choose action, the last choice has to be a state, like hover or pressed. If you choose static, the last choice has to be a surface, light or dark. So the first choice controls what the last choice is allowed to be. A list cannot show that, because every item in a list is equal. A tree can, because a branch on the left can have different endings from a branch on the right.

The order matters. Suppose you put the meaning first instead: neutral, primary, information, success, warning, danger. Under each of those six, you would still have to ask "static or action?". That is the same question asked six times. Put static or action at the top and you ask it once. You end up with exactly the same names either way. One tree asks each question once. The other keeps repeating itself.

Changes near the top cost more. Adding at the bottom is cheap. A seventh meaning adds one small branch, a few new names under it. Adding at the top is expensive. A third choice next to static and action would copy the whole tree under it, doubling it. So the choices at the top are the ones to think hardest about, because they are the ones you cannot easily change later.

Many names, one color

Several names can point at the same color. Here are three that do.

--color-static-label-danger-default    ─┐
--color-static-content-danger-default  ─┼─→  --color-red-700  #B42318
--color-static-border-danger-strong    ─┘

Three names, one value. That is not a mistake or a duplicate. It is three separate decisions that happen to agree right now. If someone later decides error borders should be lighter than error text, you change one line, and the other two stay the same. If all three had said #B42318 directly, you would have to search the whole codebase for every place that used it.

The rule only works in one direction. Many names pointing at one value is fine and normal. One name pointing at many values is not allowed, because stopping that is the whole reason tokens exist. There is one exception: light mode and dark mode. A token holds one value for light and one for dark. That is still one value per mode.

Quick preview

Design system results

You have not built anything yet. But it helps to know now where this is going. When the design system is finished, it will hold a tree of every name the rules allow, and a file of the names that were given a color. Any name anyone asks for, a person or a program, gets one of three results back.

  1. Defined. The name exists and has a color. Success text on a light surface is one, because a "Saved" message somewhere uses it. The system hands back the color and the builder uses it.
  2. Not defined. The name is spelled correctly and the rules allow it, but nobody has given it a color yet. Warning text on a dark surface is one, because nothing in the product has needed it. The system finds the box and finds it empty, so it stops and asks for a color. It never invents one, because an invented color is how two almost-matching greens get into a product.
  3. Rejected. The name cannot exist, because the rules you wrote say so. Hover on a static thing is one: static means you cannot press it, and hover only happens to things you press, so the tree has no box for it. The system finds no box at all and stops with "this is not a name". The fix is the name, not a color.

The stopping in results 2 and 3 is done by a checker you will build in stage 5, so nobody has to catch these by eye. For now, the thing to hold on to is that a good design system does not just store colors. It answers every request with one of these three, every time. The table shows one of each, with the same word painted three ways.

KindExampleWhat it looks likeWhat it meansWhat the agent should do
Defined --color-static-label-success-default
Saved
a real value: green 700 on the light surface
The token exists and has a value. Nothing. Use it where it applies.
Not defined --color-static-label-warning-inverted
Warning
a dark surface, and no value to paint the word with
Parses perfectly. But nothing in the product has ever put warning text on a dark surface, so nobody gave it a value. Stop and say so. Never improvise a hex.
Rejected --color-static-label-danger-hover
hoverrejected
nothing static can be hovered, so there is no such word to paint
Nonsense. Static things don't have hover states — that is what choosing static meant. Reject. The grammar forbids it.

What you can do now

Say what a token is in one sentence. Read any name in the tree and say what it is for. Tell a defined name from a not-defined one from a rejected one. And answer the question that starts stage 2: what does your product need to say, before it says a color?

Next · Stage 2 · Decide before you draw: the project questions, then how you will write and store the tokens, then the colors. Coming soon.

The design system series

  1. 1Introduction to design tokens You are here13 min
  2. 2Decide before you drawComing soon
  3. 3Build the three tiersComing soon
  4. 4Write it downComing soon
  5. 5Make it holdComing soon
  6. 6Take it into FigmaComing soon

Share this post

Written by Suzanne Sallaj

Product designer working on agentic AI, six years across healthcare, fintech and consumer.