DEV Community

Cover image for 🧩 Creating CSS Theme Variables from a JavaScript File
Raj Aryan
Raj Aryan

Posted on

🧩 Creating CSS Theme Variables from a JavaScript File

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',
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Looks great in JavaScript — but what about CSS?
You’ll still need something like:

:root {
  --color-brand-primary: #7B1FA2;
  --color-brand-primary-light: #BA68C8;
  ...
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

🧠 Step 2: Import the Theme

Inside index.js:

import theme from './theme.js';

console.log(theme);
Enter fullscreen mode Exit fullscreen mode

Run it:

node index.js
Enter fullscreen mode Exit fullscreen mode

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]);
  });
};
Enter fullscreen mode Exit fullscreen mode

When we run:

console.log(Object.entries(theme).flatMap(mapTheme));
Enter fullscreen mode Exit fullscreen mode

We’ll get an array like:

[
  '--color-brand-primary: #7B1FA2',
  '--color-brand-primary-light: #BA68C8',
  '--color-data-blue: #40C4FF',
  ...
]
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Run:

node index.js
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)