As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!
I want to talk about something that changed how I build websites. It’s called design tokens. If you’ve ever struggled to keep colors, spacing, or fonts consistent across a big project, this is for you. Think of design tokens as the basic building blocks of your site’s look and feel. Instead of using a raw color code like #3b82f6 everywhere, you give it a name, like color-primary-500. This simple change is powerful.
It means everyone on the team—designers and developers—starts speaking the same language. When a designer says, “Let’s use the primary color,” we both know exactly which blue they mean. More importantly, if we decide to change that blue tomorrow, I only update it in one place. Every button, link, and card using that token updates automatically. This is the core idea: turning visual style into structured, reusable data.
Let me show you what this looks like in practice. I usually start by defining all my tokens in a single JavaScript object. This becomes my single source of truth.
// design-tokens.js
export const tokens = {
color: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6', // This is our main brand blue
900: '#1e3a8a'
},
neutral: {
50: '#f9fafb',
500: '#6b7280',
900: '#111827'
}
},
spacing: {
sm: '0.5rem',
md: '1rem',
lg: '1.5rem'
},
typography: {
fontFamily: { sans: 'Inter, sans-serif' },
fontSize: { base: '1rem', lg: '1.25rem' }
}
};
This object is just data. It doesn’t do anything by itself. The magic happens when we translate this data into something our website can use. The most common way is to turn these tokens into CSS custom properties, often called CSS variables.
Here’s how I convert that tokens object into CSS.
/* styles.css */
:root {
/* Colors from our tokens */
--color-primary-500: #3b82f6;
--color-neutral-900: #111827;
/* Spacing from our tokens */
--spacing-md: 1rem;
/* Typography from our tokens */
--font-family-sans: 'Inter', sans-serif;
}
Now, I can use these variables anywhere in my CSS. I stop writing fixed values.
.button {
background-color: var(--color-primary-500);
color: white;
padding: var(--spacing-md);
font-family: var(--font-family-sans);
}
.card {
background-color: white;
border: 1px solid var(--color-neutral-200);
padding: var(--spacing-lg);
}
This approach has a huge benefit: theming. Let’s say we want a dark mode. Without tokens, this is a painful process of finding and replacing colors. With tokens, I just redefine the variables for a different theme.
/* Original light theme is on :root */
:root {
--color-background: #ffffff;
--color-text: #111827;
}
/* Override variables for dark theme */
[data-theme="dark"] {
--color-background: #111827;
--color-text: #f9fafb;
}
/* My components don't need to change */
body {
background-color: var(--color-background);
color: var(--color-text);
}
To switch themes, I just add data-theme="dark" to my HTML. All the colors update instantly because every component references the token, not a fixed value. This is the first major win.
But tokens aren’t just for CSS. I use them in JavaScript all the time. For example, I might need to set a chart’s color based on a token, or validate that a user’s input meets accessibility contrast ratios.
I create a small utility class to manage this.
// token-utils.js
class TokenManager {
constructor(tokenObject) {
this.tokens = tokenObject;
}
// A method to get any token value
get(path) {
// Split a string like 'color.primary.500' into parts
const parts = path.split('.');
let value = this.tokens;
// Navigate down the object step by step
for (const part of parts) {
value = value[part];
if (value === undefined) {
console.warn(`Token not found: ${path}`);
return null;
}
}
return value;
}
// A method to check if text color has enough contrast on a background
isAccessible(textColorPath, backgroundColorPath) {
const textColor = this.get(textColorPath);
const bgColor = this.get(backgroundColorPath);
// A simple luminance calculation (real one is more complex)
function getLuminance(hex) {
// Convert hex to RGB...
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
// ... do math to get relative luminance
return (0.2126 * r + 0.7152 * g + 0.0722 * b);
}
const l1 = getLuminance(textColor);
const l2 = getLuminance(bgColor);
const contrast = (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
// WCAG guideline requires > 4.5 for normal text
return contrast > 4.5;
}
}
// Using it
const manager = new TokenManager(tokens);
const primaryColor = manager.get('color.primary.500'); // Returns '#3b82f6'
const isAccessible = manager.isAccessible('color.neutral.900', 'color.neutral.50');
console.log(`Is contrast OK? ${isAccessible}`);
This is helpful during development. I can programmatically check if my color choices are accessible, straight from my defined tokens.
The real power comes when you connect tokens to a component library. Let’s look at a React component that uses tokens directly.
Instead of hard-coding styles, the component asks for tokens.
// A simple Box component using tokens
import { useTokens } from './token-context';
function Box({ padding = 'md', background = 'neutral.50', children }) {
// useTokens is a hook that gives us access to our token object
const tokens = useTokens();
// Find the right values from the tokens
const paddingValue = tokens.spacing[padding] || padding;
const backgroundColor = tokens.color[background] || background;
const style = {
padding: paddingValue,
backgroundColor: backgroundColor,
borderRadius: tokens.borderRadius?.md || '4px'
};
return <div style={style}>{children}</div>;
}
// Using the Box component
function App() {
return (
<Box padding="lg" background="primary.50">
This box uses token values for consistent spacing and color.
</Box>
);
}
Notice that the Box component doesn’t know what padding="lg" means. It asks the token system. If I later decide lg should be 2rem instead of 1.5rem, I update the tokens.spacing.lg value. Every Box component in my app gets the new padding automatically. This separation is crucial for scaling.
But what about projects that use Sass, or need styles for native mobile apps? Writing tokens by hand for each platform is tedious and error-prone. This is where transformation tools come in.
I write my tokens once, in a standard format like JSON, and then a build process creates the files I need.
// tokens.json
{
"color": {
"primary": { "500": "#3b82f6" }
},
"spacing": { "md": "1rem" }
}
I then use a Node.js script to convert this file.
// build-tokens.js
const fs = require('fs');
const tokens = require('./tokens.json');
// Function to convert tokens to CSS variables
function toCSSVars(tokenObj, prefix = '') {
let css = ':root {\\n';
for (const [category, values] of Object.entries(tokenObj)) {
for (const [key, value] of Object.entries(values)) {
const name = `--${prefix}${category}-${key}`.toLowerCase();
css += ` ${name}: ${value};\\n`;
}
}
css += '}';
return css;
}
// Function to convert tokens to a Swift file for iOS
function toSwift(tokens) {
let swift = 'import UIKit\\n\\n';
swift += 'enum Tokens {\\n';
for (const [category, values] of Object.entries(tokens)) {
for (const [key, value] of Object.entries(values)) {
if (category === 'color') {
swift += ` static let ${category}${key} = UIColor(hex: "${value}")\\n`;
} else if (category === 'spacing') {
// Convert rem to points (1rem = 16px typically)
const points = parseFloat(value) * 16;
swift += ` static let ${category}${key}: CGFloat = ${points}\\n`;
}
}
}
swift += '}';
return swift;
}
// Generate and save the files
fs.writeFileSync('./dist/tokens.css', toCSSVars(tokens, 'ds-'));
fs.writeFileSync('./dist/Tokens.swift', toSwift(tokens));
console.log('Token files built!');
Running this script gives me a tokens.css file for my website and a Tokens.swift file for an iOS app, both from the same source. This ensures the blue in my app is the exact same blue as on my website.
As a project grows, you need to trust your tokens. I add tests to prevent mistakes. For example, I test that all my color tokens are valid hex codes.
// test-tokens.js
import { tokens } from './design-tokens.js';
import test from 'node:test';
import assert from 'node:assert';
test('Color tokens are valid hex codes', () => {
function isValidHex(color) {
return /^#[0-9A-F]{6}$/i.test(color) || /^#[0-9A-F]{8}$/i.test(color);
}
// Flatten the color object and check each value
const allColors = [];
function collectColors(obj) {
for (const value of Object.values(obj)) {
if (typeof value === 'string') {
allColors.push(value);
} else if (typeof value === 'object') {
collectColors(value);
}
}
}
collectColors(tokens.color);
for (const color of allColors) {
assert.ok(
isValidHex(color),
`"${color}" is not a valid hex color code.`
);
}
});
I can run this test whenever I change the tokens file. If someone accidentally types #3b82f (missing a digit), the test fails and catches the error before it reaches users.
You might wonder about the naming of tokens. Is primary-500 better than brand-blue? I prefer a systematic scale. The number 500 represents the base shade. 100 is lighter, 900 is darker. This gives designers a predictable palette.
color: {
primary: {
100: '#dbeafe', // Very light blue
500: '#3b82f6', // Base blue
900: '#1e3a8a' // Very dark blue
}
}
A designer can say, “Use primary-700 for the hover state,” and I know it will be a darker shade of the same hue, guaranteed to look good. It removes guesswork.
For spacing, I use a similar scale. spacing.1 might be 0.25rem, spacing.2 is 0.5rem, and so on. Or I use names like xs, sm, md. The key is consistency. Every margin, padding, and gap in the UI should come from this short list. This stops one developer using 11px and another using 0.6875rem for what should be the same visual space.
Let’s talk about integrating this into a real workflow. When I start a new project with a designer, we agree on the core tokens first. We decide the primary color, the neutral grays, the type scale, and the spacing unit. We document this in a shared, simple JSON file.
The designer uses these token names in their design tool (like Figma). Instead of labeling a rectangle with #3b82f6, they label it Primary/500. When they hand the design to me, I already know which variable to use.
In my code, I set up a pipeline. The shared JSON file is committed to our repository. A CI/CD job runs the transformation script, generating the CSS, Swift, and Android XML files. These generated files are then imported into the respective projects.
This process creates a closed loop. If the designer changes Primary/500 to a new hex value in the JSON file, the CI job runs, the CSS updates, and the website deploys with the new color. I didn’t write a single line of CSS for the change.
There are advanced techniques too. You can use tokens for more than just colors and spacing. I’ve used them for animation durations, shadow definitions, and even border widths.
tokens: {
animation: {
duration: { fast: '150ms', base: '300ms', slow: '500ms' }
},
shadow: {
low: '0 1px 3px rgba(0,0,0,0.12)',
high: '0 10px 25px rgba(0,0,0,0.15)'
}
}
Then, a modal component can use tokens.animation.duration.base for its fade-in, ensuring all animations in the app share the same rhythm.
The goal is to push every visual decision into the token system. Over time, you find you hardly ever write raw CSS values. You just compose components using tokens. This makes the UI incredibly consistent and easy to refactor.
I also use tokens to create utility classes, similar to frameworks like Tailwind CSS. I can generate a set of classes from my spacing tokens.
/* Generated from spacing tokens */
.p-sm { padding: var(--spacing-sm); }
.p-md { padding: var(--spacing-md); }
.m-sm { margin: var(--spacing-sm); }
.m-md { margin: var(--spacing-md); }
This gives developers quick, token-backed utility classes without sacrificing the single source of truth.
If you’re starting a new project, I recommend beginning with tokens from day one. Define a handful: your primary color, a neutral gray, a base font size, and a spacing unit. Use them in your first component.
As the project grows, add new tokens when you need a new value. Ask: “Is this a new concept, or a different version of an existing one?” A new shade of blue is probably primary-600. A color for error messages is a new concept, so you might add an error category.
For large teams, document your tokens. Write down when to use neutral-500 vs neutral-600. This documentation becomes the style guide for your application.
In summary, design tokens are a method for managing style as data. They bridge the gap between design intent and developer implementation. By investing in this system early, you save countless hours of manual updates, reduce bugs, and create a foundation for a cohesive, scalable, and maintainable user interface. The initial setup requires thought, but the long-term payoff in consistency and team harmony is immense. Start small, be consistent, and let the tokens do the work.
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | Java Elite Dev | Golang Elite Dev | Python Elite Dev | JS Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva
Top comments (0)