One source of truth for your design system — without manual duplication.
💡 Introduction
Ever changed a brand color in your design system and spent half your day hunting it down in both JavaScript and CSS? Yeah, that pain is universal.
In modern web projects — especially those involving data visualizations, dashboards, or multi-brand UIs — keeping your theme consistent between JavaScript and CSS is essential. But maintaining two parallel systems (a JS theme and a CSS variables file) is like maintaining two separate hearts for one body — inefficient and prone to drift.
So, what if you could generate your entire CSS theme automatically from a JavaScript file?
That’s exactly what we’ll do today.
🎨 The Problem: Duplicated Color Definitions
Imagine defining your theme like this:
const theme = {
color: {
brand: {
primary: {
DEFAULT: '#7B1FA2',
light: '#BA68C8',
dark: '#4A148C',
},
secondary: {
DEFAULT: '#E91E63',
light: '#F48FB1',
dark: '#C2185B',
},
},
data: {
blue: '#40C4FF',
turquoise: '#84FFFF',
mint: '#64FFDA',
},
},
};
Looks great in JavaScript — but what about CSS?
You’ll still need something like:
:root {
--color-brand-primary: #7B1FA2;
--color-brand-primary-light: #BA68C8;
...
}
That’s where the nightmare begins — two files, double maintenance, and guaranteed inconsistency after your third late-night refactor.
Let’s fix that.
⚙️ Step 1: Project Setup
Make sure you have Node.js and npm installed.
Run the following to initialize your project:
npm init -y
Then create a file named theme.js and paste your theme object there.
Also, create a index.js — that’s where our automation script lives.
touch theme.js index.js
🧠 Step 2: Import the Theme
Inside index.js:
import theme from './theme.js';
console.log(theme);
Run it:
node index.js
If your theme logs correctly — you’re golden. Now we’ll transform that object into CSS variables.
🪄 Step 3: Mapping JS Theme to CSS Variables
We’ll need a function that takes a deeply nested JS object and flattens it into CSS variable definitions.
Here’s the magic function:
const mapTheme = ([key, value]) => {
if (typeof value === 'string') {
return `--${key}: ${value}`;
}
return Object.entries(value).flatMap(([nestedKey, nestedValue]) => {
const newKey = nestedKey === 'DEFAULT' ? key : `${key}-${nestedKey}`;
return mapTheme([newKey, nestedValue]);
});
};
When we run:
console.log(Object.entries(theme).flatMap(mapTheme));
We’ll get an array like:
[
'--color-brand-primary: #7B1FA2',
'--color-brand-primary-light: #BA68C8',
'--color-data-blue: #40C4FF',
...
]
Clean, structured, and scalable.
🔁 Step 4: Writing to a CSS File
Now let’s export those variables into an actual CSS file so your app can use them.
import { writeFile } from 'fs/promises';
import theme from './theme.js';
const mapTheme = ([key, value]) => {
if (typeof value === 'string') {
return `--${key}: ${value}`;
}
return Object.entries(value).flatMap(([nestedKey, nestedValue]) => {
const newKey = nestedKey === 'DEFAULT' ? key : `${key}-${nestedKey}`;
return mapTheme([newKey, nestedValue]);
});
};
const buildTheme = async () => {
try {
const result = Object.entries(theme).flatMap(mapTheme);
let content = result.map((line) => `\t${line};`);
content = [':root {', ...content, '}'].join('\n');
await writeFile('src/theme.css', content, { encoding: 'utf-8' });
console.log('✅ CSS file written successfully!');
} catch (e) {
console.error('❌ Error:', e);
}
};
buildTheme();
Run:
node index.js
Your project now has src/theme.css automatically generated from your JS theme.
🌈 The Result
Your CSS file will look like this:
:root {
--color-brand-primary: #7B1FA2;
--color-brand-primary-light: #BA68C8;
--color-brand-primary-dark: #4A148C;
--color-brand-secondary: #E91E63;
--color-brand-secondary-light: #F48FB1;
--color-brand-secondary-dark: #C2185B;
--color-data-blue: #40C4FF;
--color-data-turquoise: #84FFFF;
--color-data-mint: #64FFDA;
}
Now both your CSS and JS share the same source of truth.
Update one color in your theme.js, rerun the script — and voilà, your entire design system stays in sync.
🧩 Why This Matters
No more duplication.
One source of truth — your JS theme.Works with any design system.
Extend beyond colors — typography, spacing, breakpoints — all can be auto-generated.Future-proof your codebase.
As your app scales, your theme stays manageable and consistent across frameworks (React, Vue, or even plain HTML).
🚀 Bonus: The Recursive Power
This solution isn’t limited to colors — it works for any nested object structure.
That means you can generate CSS variables for spacing, fonts, shadows, transitions, or even brand-specific theming.
🧩 Final Thoughts
This approach merges two worlds — JavaScript flexibility and CSS custom property power.
You write your theme once, and your build script ensures perfect alignment between logic and presentation.
If you’re serious about design consistency and developer happiness,
automate your theme.
✍️ Written by Er Raj Aryan
Senior Software Engineer | Frontend Architect | Lover of clean design systems and declarative CSS
Top comments (0)