I often get asked how to keep a color system consistent when a project spans the web, animated interfaces, and printed material. I used to manage separate color palettes for each medium—one for the website, another for motion assets, and a third for print—and it quickly became a maintenance headache. Moving colors into a single source of truth with CSS variables changed the game for me. In this article I’ll share a practical approach to designing color systems that scale across web, motion, and print while keeping accessibility, flexibility, and maintainability front of mind.
Why single source of truth matters
A shared color system reduces duplication, avoids drift between platforms, and speeds up iterations. When a brand tweak is needed—say, a subtle shift to the primary hue—you don’t want to chase down eight different files. CSS variables let you centralize color tokens and then adapt them per context (web, motion, print), which is especially handy when teams include designers, front-end engineers, and motion designers.
Start with tokens, not hex codes
Before you even write a line of CSS, define your color tokens. Tokens are meaningful names that represent intent rather than appearance. For example:
- --color-brand-primary — the primary brand color used for actions and links
- --color-ui-bg — background surfaces
- --color-text-high — high-contrast body text
- --color-accent-1 — secondary accent for highlights
Tokens let you evolve the visual system without changing usage across components. If you decide the brand should be slightly warmer, you update --color-brand-primary and everything that references that token updates automatically.
Structure your CSS variables
I like organizing tokens into layers: core (base colors), scales (tints/shades), and semantic (purpose-driven tokens). Here’s a simple example I often use as a starting point:
Define core colors
:root {
--core-black: 0 0% 10%; /* HSL (h s% l%) split for easier manipulation */
--core-white: 0 0% 100%;
--brand-h: 210; /* hue */
--brand-s: 90%; /* saturation */
--brand-l: 50%; /* lightness */
}
Using HSL components stored in variables helps for programmatic adjustments (darkening, increasing saturation) that are useful across web and motion.
Generate scales for UI and motion
From the core brand HSL, create a scale for UI states and motion-friendly variations. I favor named steps like 100–900 because they communicate relative lightness:
:root {
--brand-500: hsl(var(--brand-h) var(--brand-s) var(--brand-l));
--brand-300: hsl(var(--brand-h) var(--brand-s) calc(var(--brand-l) + 20%));
--brand-700: hsl(var(--brand-h) var(--brand-s) calc(var(--brand-l) - 15%));
}
For motion, you’ll want slightly more saturated or brighter variants for animated states to preserve perceived contrast during transitions. Keep a set of motion-specific tokens like --brand-motion-hover or simply use the scale (e.g., --brand-400) consistently in animations.
Design for accessibility from the start
I treat contrast checks like table stakes. Use tokens to define text and background relationships so you can run automated checks. Example semantic tokens:
:root {
--color-text-high: var(--core-black);
--color-text-low: hsl(0 0% 60%);
--color-ink-on-brand: color-mix(in srgb, var(--brand-500) 20%, black);
}
Note: color-mix is part of modern CSS and useful for generating accessible ink colors. When color-mix isn't available, precompute contrasts and create tokens for accessible combinations. Also keep in mind contrast can change in motion—fast color changes can reduce effective contrast—so prefer animations that don’t rely solely on color for critical states.
Bridging to print (and CMYK)
Print uses CMYK and often different color profiles, so you can’t rely on exact CSS colors to match press output. But tokens still help.
- Export a design system color guide: For each token, include sRGB values and a recommended CMYK recipe. Tools like Adobe Color, Figma, or a Pantone bridge can help produce CMYK equivalents.
- Keep a mapping table in your docs for designers and printers. Example:
| Token | sRGB | CMYK | Pantone (if available) |
| --brand-500 | #0077FF | 100 50 0 0 | PMS 300 C |
If close visual matching is critical, involve your print vendor early. But from a systems perspective, naming and documenting these mappings keeps everyone aligned.
Motion-specific considerations
Motion designers often use After Effects or Lottie. You can export tokens to JSON so motion pipelines can read the same values. Here’s a workflow I’ve used:
- Maintain a tokens JSON (e.g., tokens/colors.json) with HSL/hex values.
- Motion team imports that JSON and maps tokens to animation properties.
- When tokens change, the same JSON update drives both CSS and animation assets.
In CSS, take advantage of transition-friendly color spaces like lab() or use HSL with small lightness shifts for smoother perceived transitions. If you animate color directly in the browser, prefer transition: color 160ms ease or 100ms cubic-bezier(…) and test for flicker on different displays.
Practical tooling and automation
A few practical tools speed everything up:
- Design tokens: use Style Dictionary or Theo to export tokens from a single source into CSS, JSON, SCSS, or platform-specific formats.
- Figma tokens plugin: sync design tokens directly from design files.
- Contrast testing: axe-core, Lighthouse, or Contrast Grid during design reviews.
- Color utilities: tinycolor2 or chroma.js for programmatic transforms if you need dynamic variations.
Set up a build step to generate CSS custom properties from your token source. Example: Style Dictionary -> tokens.json -> build -> dist/colors.css. This keeps your token definitions authoritative and eliminates hand-editing multiple files.
Variants and themes
Supporting light/dark themes is straightforward with CSS variables. Define theme overrides instead of new tokens:
:root { --color-bg: #fff; --color-text: #111; }
[data-theme="dark"] { --color-bg: #0b0b0b; --color-text: #eaeaea; }
For print, you might include a separate stylesheet (or token mapping) that substitutes sRGB tokens with print-friendly equivalents. The important part is keeping the semantic token names consistent so components are theme-agnostic.
Documentation that keeps teams aligned
Having a living style guide is non-negotiable. Your docs should include:
- Token list with names, usage guidance, and examples
- Contrast ratios and accessibility notes
- Motion variants and recommended easing/durations
- Print mappings and vendor notes
- Exportable JSON/CSS/SCSS sources
I recommend a single source repository for tokens plus a small site (Storybook or a static docs site) that showcases tokens in context. When designers, developers, and motion artists can view live examples, adoption grows and mistakes drop.
How I apply this in real projects
In a recent rebrand project, I created a tokens.json and used Style Dictionary to produce CSS variables, a Figma tokens file, and a JSON bundle for motion. The motion team used the JSON in After Effects via a script, and our print partner got a PDF swatch with CMYK values. The result: fewer mismatches, faster rollout, and a clear process for future updates.
Next time you set up a color system, start with tokens, automate exports to every platform you need, and don't treat print or motion as afterthoughts. Once the tokens are living in one place, maintaining color consistency becomes a workflow problem you can solve—rather than a never-ending design scavenger hunt.