Color Systems: Scales, Tokens & Dark Mode
Color theory courses love clean examples. A single card component. A hero section at 1440px, full brightness, on a calibrated monitor. Everything in its happy state, no edge cases, no inheritance from a previous design system, no engineer asking why there are four slightly different blues in the Figma file.
Learn on the move: press play and turn your commute, walk or drive into time that grows your design career.
Listen to the full Color Systems: Scales, Tokens & Dark Mode course on DesignPath
Real product work is different. Color decisions happen inside constraints you did not choose. An existing brand guide, a codebase already using certain hex values, or a palette that works fine for the main flow and falls apart in the data visualization screen nobody noticed until now.
This module is where theory meets those conditions.
Color Scales & Tokens
Your brand guidelines hand you a single hex code. "This is our blue." Great. But your product needs a pale blue for large backgrounds and a slightly-less-pale blue for card surfaces. Then a medium blue for borders, the actual brand blue for CTAs, a darker blue for hover states, and an even darker one for pressed. One hex code was never going to be enough.
This is why color scales exist - and why building them is one of the first things a serious design system does.
The Three Roles Your Colors Fill
Before you start building scales, it helps to know what roles you're actually filling. Every color in a product system belongs to one of three categories:
Brand - the one or two colors that carry your identity. Used sparingly, for the things that matter most: primary actions, key moments in the flow, the elements you want users to remember. Spend brand color like money; the more places it appears, the less power it has in any of them.
Neutrals - the grays (or near-grays) that do most of the actual work. Text, borders, backgrounds, surfaces, dividers. You need more of these than you think - typically 8-10 steps from near-white to near-black, because hierarchy on a real screen is built almost entirely out of neutrals, not brand color.
Semantic - colors that mean something fixed: success, warning, error, info. These are a vocabulary, not decoration. Once red means "destructive action," it can't also be a brand accent. Keep semantic colors out of the brand palette and out of the neutral palette - they have one job.
This three-role structure is what you're building when you build a color system. Each role needs its own scale.
The most common mistake is letting roles bleed into each other - especially brand into semantic. The day your brand primary is the same red you use for errors, the whole system breaks: users can't tell whether a red element is a brand moment or a warning. Establish which colors belong to brand expression and which belong to the functional UI language before you start building anything.
What a Color Scale Is
A color scale is a structured range of values for a single hue, arranged from lightest to darkest. The convention, popularized by Tailwind CSS and used by most modern design systems, runs from 50 (near white) to 950 (near black), with stops at 100, 200, 300, 400, 500, 600, 700, 800, and 900.
Each stop is a distinct shade - light enough to use as a background at the 50 end, dark enough to use as text or for maximum contrast at the 900 end. The brand color typically sits somewhere in the 500-600 range, which is the visually "full" zone for most hues.
A complete system usually includes a scale for:
- The primary brand hue (your blue, green, orange, etc.)
- Gray (the workhorse of UI - most text, surfaces, and borders are gray)
- Semantic hues: red (error), green (success), yellow/orange (warning), and blue if it's not also the primary
That might sound like a lot, but each scale gives you exactly what you need when you need it, and eliminates the guesswork of "which shade should I use here."
Building a Scale: The Approach
The goal of a scale is perceptual evenness - each step should feel like roughly the same visual distance from the step above and below it. Equal numerical differences in hex or HSL don't produce equal perceptual differences, so you can't just divide the range mechanically.
In practice, there are three main approaches:
Use a generator.Tools like Radix Colors, Palettante, or Leonardo (Adobe) take a base color and generate a full perceptually-even scale automatically. This is the fastest path and often the best one - these tools account for human perception in ways that manual work doesn't.
Build in HSL with manual adjustment.Start from your brand color in HSL. Hold the hue constant (or let it shift slightly - blues often benefit from a slight hue shift toward purple in the darks and toward cyan in the lights). Increase lightness for lighter stops, decrease for darker. Then eyeball the result and adjust any stops that feel too close together or too far apart.
Borrow and adapt.Use an open-source system like Tailwind, Ant Design, or Material as your base and adjust the 500-stop to match your brand color, shifting the surrounding steps proportionally. Faster than building from scratch, though the result may not be perfectly tuned to your brand hue.
Whichever approach you use, test the scale against real use cases before committing. The 100 should work as a large background without looking like pure white. The 200 should work as a card or panel surface. The 700+ should be dark enough for text or icons while still reading as the brand color.
Design Tokens
Once you have scales, you need a way to use them consistently across design and code. That's what design tokens are for.
A design token is a named variable that stores a single value - a color, a spacing unit, a font size, a shadow. Instead of hardcoding `#2563EB` into a button component in both Figma and CSS, you reference the token `action-primary`, which resolves to `blue-600` in the scale, which resolves to `#2563EB`.
Tokens work in two layers (as covered in the previous article on semantic color):
- Primitive tokens: The raw scale. `blue-600 = #2563EB`
- Semantic tokens: The role mappings. `action-primary = blue-600`
Components use semantic tokens. Semantic tokens reference primitives. When you change a primitive, every semantic token using it updates. When you change a semantic mapping (say, remapping `action-primary` from `blue-600` to `blue-700`), every component using `action-primary` updates.
This chain - component references semantic token references primitive token - is what makes large design systems maintainable.
Pro tip: If you're building a scale from scratch and want a quick sanity check, view the scale in grayscale. The steps should look evenly distributed from light to dark. If several consecutive steps look the same in grayscale, they're too close together and will blend in real use.



