Skip to content

UX-01 · UX · Digital Product Design

Managing color in code: design systems and color tokens

2,911 words · about 19 min listening

Read the Turkish original

Glance at the summary firstThe summary from the end of the lesson, as a road map before you start reading.
  1. Writing a hex code directly into a button loses the color's role; a change turns into a search job, near-copies slip past, and every new theme requires handling every copy separately.
  2. Color tokens are mostly organized in three layers: a reference palette and tonal scales that carry no meaning, semantic tokens that name roles, and tokens that belong to components; the layers reference each other through aliases.
  3. Tokens are kept in a single file and distributed to design tools and code from the same place; a theme or a fix reaches the whole product by changing references, without touching components, and can be tested from a single place across images of people with different skin tones.

Short answer

Writing a hex code directly into a button loses the reason that color is there in the first place: the code only says "this blue," not "this is the primary action." When you want to change the color, you have to find every one of its hundreds of copies one by one; copies written with small differences slip past the search, and adding a second look, like a dark theme or a high-contrast mode, means going through every copy again by hand. Color tokens solve this by splitting the problem into layers: the raw values live in one place, the names that describe their roles live in another, and a component only calls the role.

The cost of hardcoding

In a product's first version, everything moves fast. A designer picks a blue, a developer writes it into a button's background, writes it again on the next screen as a link color, and writes it once more on the border of a notification box. The value is the same hex string everywhere, and no one thinks it's a problem.

The problem becomes visible once the product grows. When the team wants to darken the brand blue a little, they search the codebase for those six characters and run into two things. The first is copies of the same blue written with one or two digits different: one eyeballed from a screen, one copied from an old version of a design file. The search misses these, and after the change, two blues start living inside the product. The second is sneakier: not every copy the search finds does the same job. The same blue is a primary button's background in one place, a link's color in another, and just a decorative line somewhere else. If the brand blue changes, should the links change too? The code can't answer that question, because the code holds only a value, not an intent.

The same problem grows when a new look is added. A dark theme, a high-contrast mode, or a different color theme for a sub-brand requires handling every hardcoded value separately. This is the answer to the question on the lesson card: writing a hex code directly into a button breaks nothing today, but it turns every future change into a job of searching and guessing.

What is a token

The community working to standardize the file format for design tokens, the Design Tokens Community Group under the W3C, published its first stable format specification under the name 2025.10. The document defines tokens as a way of expressing design decisions in a platform-independent form; the goal is to build a shared vocabulary across different disciplines, tools, and technologies. A token's only required field is its value; its type, description, and other fields are optional. A color-type token's value is given not just as a hex string, but as an object that also states which color space it's written in. GT-10 showed that a hex code is incomplete without stating its color space; the specification builds that gap into the format itself.

That last sentence matters: this document is not a W3C standard, and it doesn't bind any tool on its own. But it writes down what many teams already do into a shared file format, and it opens the way for a token file produced by a design tool to be converted, by a build tool, into web, iOS, and Android code.

Three layers

A token's power doesn't lie in its name alone, but in names being able to reference each other. Most design systems organize colors in three layers.

The reference palette collects every color the brand and the interface can use. Names carry no meaning at this layer: "blue 600" is a blue; it doesn't know whether it's a primary action. Colors at this layer are mostly organized as tonal scales.

A common convention is to give a hue's light end the number 50 and its dark end 900 or 950, with steps of a hundred in between. In Tailwind CSS's default palette, every color carries eleven steps: 50 is the lightest, 950 is the darkest, and the steps in between run from 100 to 900 in hundreds. The same palette's color values are written in OKLCH. That's not a coincidence: for the scale's steps to look evenly spaced to the eye, lightness has to be measured in a space that writes the differences the eye takes as equal steps with equal numbers; lightness in HSL can't do that job. OKLCH and OKLab themselves are BI-24's subject, and how light and dark tints are formed is the subject of DT-07. The gaps between the numbers leave room for a new tint to be added between two steps someday, if needed.

The middle layer carries the design system's real decisions. A token named "primary" gets its value by referencing "blue 600." A token named "text on primary" sets the color of the text that will be written over the primary background, and gets its value by maybe referencing "white." These two tokens are always thought of together, because the contrast between them has to meet the ratio in UX-06. Which color tells success and which tells error, and where those meanings break down, is UX-02's subject; what matters about this layer here is that the name carries a role, while the value carries that role's color today.

The top layer belongs to components. A token named "filled button background" references "primary." If someday only buttons need to be a little darker, this single token gets linked to a different semantic or reference value, and the rest of the product is unaffected.

It's these references that tie the three layers together. Material Design's web components write this structure out explicitly: every component token links to a system token, and every system token links to a concrete reference value; on the web, these tokens live as CSS custom properties. In an example from the same documentation, an error-type button's background color isn't given directly as a color, but as a reference to the system's "error" color; the button's text is likewise tied to the "on error" color.

Themes are a single reference change

The layers' real payoff shows up when a second look is added. In a dark theme, the "surface" token links not to a light gray but to a dark gray; "text on surface" flips from dark to light; "primary" maybe moves a step lighter, because the contrast between it and the text needs to be re-established over a dark background. None of these changes touch a component: buttons, cards, and forms keep calling the same semantic names, and it's the reference values those names link to that change. Why a dark theme isn't just a matter of inverting colors is UX-03's subject; what's enough for this lesson is that a theme can be written as a token map.

The same logic applies to a high-contrast mode, a sub-brand, or a campaign theme. Each is just another way of linking the semantic layer to the reference palette.

A single source between design and code

The second problem tokens solve is the design file and the code drifting apart from each other over time. A designer updates the primary color in a design tool, a developer keeps using the old value in code, and the two sides see different products for months.

In mature teams, tokens sit as a single file in a version control repository. A build tool generates CSS custom properties for the web and its own formats for mobile platforms from this file; the design tool also pulls its tokens from the same file. A color change is a one-line edit in this file, goes through a code review, gets a version number, and reaches every platform with the same value. This file does the same job, in a different setting, as the master reference in GT-10's brand guide: one is the single true definition of a color for the print shop, the painter, and the fabric maker, the other is the single true definition of a color for screens. In a well-built system, the brand color in the token file's reference palette is a screen value derived from the guide's master reference.

Moving an existing product to tokens

Most teams don't start with tokens from scratch; they convert a product full of hardcoded values. The first step of this move is an inventory: every color value in the codebase is collected, and how many different values are used in how many places is worked out. The inventory is often more crowded than expected, because eyeballed near-copies count as separate values.

Then values that are very close to each other are clustered, and a reference palette step is chosen for each cluster. This is a design decision, not an automatic merge: two grays close to each other might carry two separate roles, one deliberately chosen a bit darker. Once clustering is done, every place a value is used is examined one by one, and a role name is put in place of the raw value. At this stage it turns out that some places actually carry the same role, while others fit no role at all; the latter either get a new semantic token or signal that the design needs a second look. By the end of the move, no directly written color value should remain in the codebase; enforcing this with a linting rule stops new hardcoded values from quietly creeping back in.

Example — the name label in a video call

A video call app places a small label in the bottom-left corner of each participant's video, showing their name. In the first version, the label is drawn with white text on a semi-transparent black background; both values are written directly into the component.

A complaint from users shows that the problem is a structural one, not a value one. When a participant with dark skin and dark clothing sits close to the camera in a dim room, they barely separate from the video behind the semi-transparent black background, and the label's edge disappears. When a light-skinned participant sits in front of a bright window, the label is readable, but the background unnecessarily darkens part of their face. A single fixed color doesn't fit every skin tone and setting that might fall behind it at once.

The team first ties the label to tokens: two semantic tokens named "label background over video" and "label text over video," and component tokens reference those in turn. Then they change the design decision in a single place: instead of semi-transparent black, the background links to a more opaque, neutral dark gray, and a third token is added for a thin, light border line around the label. This way, the label keeps its edge over both a dark and a light image. The change also carries over to the high-contrast theme on its own, because that theme links the same semantic names to different reference values. To test the change, the team tries the label again on an image set spanning skin tones from lightest to darkest, dim rooms, and window-lit seats; with hardcoded values, this test would have meant a separate fix for every screen.

Common misconception

Common mistake"A token is just another name for a hex code; writing "blue-600" is enough." A reference palette name also names a value, not a role.
Correction

If a component calls "blue-600" directly, hardcoding's problems have just been renamed; the name a component should call is a role name, like "primary."

Common mistake"The more layers, the better the system."
Correction

Every layer adds a link to the reference chain and makes it harder to trace where a color comes from. For a small product, a reference palette and a semantic layer are often enough; component tokens get added only when a component-level adjustment is actually needed.

Summary

  1. Writing a hex code directly into a button loses the color's role; a change turns into a search job, near-copies slip past, and every new theme requires handling every copy separately.
  2. Color tokens are mostly organized in three layers: a reference palette and tonal scales that carry no meaning, semantic tokens that name roles, and tokens that belong to components; the layers reference each other through aliases.
  3. Tokens are kept in a single file and distributed to design tools and code from the same place; a theme or a fix reaches the whole product by changing references, without touching components, and can be tested from a single place across images of people with different skin tones.

Test yourself

  1. (Easy) How many steps does a color carry in Tailwind CSS's default palette, and what numbers do the lightest and darkest steps take?
  2. (Easy) According to the Design Tokens Community Group's format specification, what is a token's only required property?
  3. (Medium) Why does writing a hex code directly into a button create a problem in the long run?
  4. (Medium) What's the difference between a reference palette token and a semantic token?
  5. (Hard) Why doesn't adding a dark theme to a product built with token layers require touching components?
  6. (Hard) Why was the name label problem in the video call app a structural problem, not a color value problem?

Answers

  1. Eleven steps; 50 is the lightest, 950 is the darkest ("Three layers").
  2. Its value ("What is a token").
  3. Code carries only the color's value, not its role; a change requires searching out every copy of that value, copies written with a digit or two different slip past the search, places where the same value does different jobs can't be told apart, and every new look, like a dark theme, requires handling every copy separately ("The cost of hardcoding").
  4. A reference palette token names a value by its hue family and step, without loading any meaning onto it; a semantic token names a role and gets its value by referencing the reference palette ("Three layers").
  5. Components call semantic names; what changes in a dark theme is the reference values those names link to. Because the names components call stay the same, the components don't change ("Themes are a single reference change").
  6. Both values had been written directly into the component, and a single fixed color didn't fit every skin tone and lighting condition that might fall behind it. Once the values were linked to tokens, the decision changed in a single place, carried over to the high-contrast theme too, and could be tested once, on a single image set spanning different skin tones and lighting ("Example — the name label in a video call").

Sources

  • Design Tokens Community Group, "Design Tokens Format Module," 2025.10.
  • Material Web, "Material Theming," official documentation.
  • Tailwind CSS, "Colors," official documentation.

Next step

UX-02 goes into the semantic layer: it discusses which colors success, warning, error, and information roles get mapped to and why this mapping is so widespread, the cases where that mapping breaks, and how, in an interface where everything warns with color, the user stops seeing the warnings. UX-03, meanwhile, unpacks this lesson's theme map: it covers why dark grays are chosen instead of pure black when producing a dark theme from the same token structure, and why surfaces get lighter as they rise.

Animation and narration notes

Animation and narration notes

Video flow

  • 0:00–0:40 Hook: Hundreds of copies of the same blue glow across a codebase; a few are a shade off.
  • 0:40–2:30 Story: The brand blue darkens; the search finds some copies and misses others; two blues appear inside the product.
  • 2:30–7:00 Explanation: The reference palette, tonal scale, semantic token, and component token are built up as stacked layers; alias arrows connect the layers.
  • 7:00–8:30 Aha moment: A single reference changes, and the whole interface switches to the dark theme; no component moves.
  • 8:30–10:30 Single source: Arrows run from one token file to the design tool and to three platforms.
  • 10:30–12:30 Real world: The name label in a video call; participants with clearly distinct skin tones, in different lighting; the label first disappearing, then becoming readable in every image after a single fix.
  • 12:30–13:00 Closing: The codes UX-02 and UX-03.

Visual notes

  • The codebase, design tool, and call app are fictional interfaces; no real product's screen or logo is used.
  • The participants in the call are drawn by the team; skin tones, clothing, and room lighting vary noticeably.
  • Token names appear on screen with their Turkish role names, and their English equivalents are given as captions.