Design systems · Stage 1 of 6 · Understand what you are about to build
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.
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;
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.
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.
background: #17B26Abackground: #17B369Two greens that almost match. Nothing connects them, so nothing catches the difference.--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.--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.--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)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 meaningbackground: var(--color-action-bg-primary-default);the button · asks for its job name"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.
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
| Slot | Options | Question it answers |
|---|---|---|
| kind | color · space · radius · text · border-width | What sort of value is it? |
| nature | static · action | Can you click it? |
| property | label · content · border · bg (background) | Which part of it? |
| intent | neutral · primary · information · success · warning · danger | What does it mean? |
| variant | states: default hover pressed selectedsurfaces: 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.
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.
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.
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.
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.
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.
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
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.
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.
| Kind | Example | What it looks like | What it means | What 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. |
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