I opened the pull request and immediately felt a headache coming on. We just acquired a smaller competitor. The product manager wanted their branding applied to our existing React components by Friday. The proposed solution was adding a massive class name to every single component in the repository. That meant thousands of file changes. It also meant duplicating all our CSS just to manually swap out a few hex codes. This is the exact moment a clean codebase turns into a legacy nightmare.
You do not want to hardcode brand names into your component logic. You definitely do not want to maintain three different button stylesheets. We need a way to swap the entire visual identity of the application from a single entry point. The best way to do this is using CSS custom properties tied to data attributes on your root element.
The messy alternative
People usually try to solve this by creating separate CSS files for each brand. They load brand-a.css or brand-b.css depending on the user. This sounds simple at first. Then you realise you have to manage cache invalidation for multiple stylesheets. You also notice a flash of unstyled content when a user switches brands on the fly.
Another common mistake is heavily nesting CSS selectors. Developers write massive blocks of code targeting specific wrapper classes. This bloats your CSS bundle. It also creates a specificity war where you constantly need to use important tags just to change a background colour. We can do much better.
Prerequisites for this approach
You need a basic understanding of CSS variables. You also need Node installed on your machine to run a simple script. We are going to take a raw JSON file containing our design decisions and convert it into a scalable CSS structure.
Your design team should provide a JSON file with the brand colours. Ask them to organise it by brand name. This keeps the source of truth clean and readable.
The raw design data
Let us look at a typical JSON structure. We have a base application and our newly acquired brand. We call the original brand core and the new one acme.
{
"core": {
"primary": "#2563eb",
"surface": "#ffffff",
"text": "#1e293b"
},
"acme": {
"primary": "#dc2626",
"surface": "#f8fafc",
"text": "#0f172a"
}
}
This is very easy to read. But browsers cannot understand this JSON directly. We need a way to transform this into something our web app can use.
Writing the build script
We will create a small Node script to read this JSON and generate our CSS. Create a file called build-theme.js in your project folder. We are going to use standard JavaScript to parse the object and generate strings of CSS.
const fs = require('fs')
const rawData = fs.readFileSync('./tokens.json', 'utf8')
const tokens = JSON.parse(rawData)
let cssOutput = ''
for (const brand in tokens) {
cssOutput += `[data-brand="${brand}"] {\n`
const colours = tokens[brand]
for (const role in colours) {
cssOutput += ` --color-${role}: ${colours[role]};\n`
}
cssOutput += `}\n\n`
}
fs.writeFileSync('./themes.css', cssOutput)
console.log('Successfully generated multi-brand CSS')
This script does something quite specific. It loops through each brand in our JSON file. It creates a CSS selector using a data attribute. Then it maps every colour role to a CSS custom property.
The compiled output
Run the script in your terminal using Node. You will see a new file called themes.css appear in your folder. Let us open it up and see what the script actually built for us.
[data-brand="core"] {
--color-primary: #2563eb;
--color-surface: #ffffff;
--color-text: #1e293b;
}
[data-brand="acme"] {
--color-primary: #dc2626;
--color-surface: #f8fafc;
--color-text: #0f172a;
}
This is exactly what we want. We have completely isolated our variables based on a single HTML attribute. The specificity is exactly the same for both brands. This means we never have to fight the cascade to override a colour.
Applying the theme in your app
Now you just need to apply these variables to your components. Your React components no longer care about which brand is active. They only care about the semantic role of the colour.
.button {
background-color: var(--color-primary);
color: var(--color-surface);
padding: 8px 16px;
border-radius: 4px;
border: none;
}
.page-wrapper {
background-color: var(--color-surface);
color: var(--color-text);
min-height: 100vh;
}
The component CSS is incredibly clean. There are no nested brand selectors. There are no media queries checking for system preferences here either. The button just asks the browser for the primary colour. The browser looks up the tree to find the value.
Switching brands dynamically
The real magic happens in your application root. You just need to change one attribute on the body tag or your main application wrapper. In a React application you might store the active brand in your global state or context.
import { useState } from 'react'
import './themes.css'
import './components.css'
export default function App() {
const [activeBrand, setActiveBrand] = useState('core')
return (
<div data-brand={activeBrand} className="page-wrapper">
<header>
<h1>Welcome to the product</h1>
<button
className="button"
onClick={() => setActiveBrand(activeBrand === 'core' ? 'acme' : 'core')}
>
Toggle Brand Identity
</button>
</header>
</div>
)
}
When you click the button the state updates. React re-renders the wrapper div with the new data attribute. The browser instantly repaints every component using the new CSS variables. There is no network request for a new stylesheet. There is no flash of unstyled content.
This approach scales beautifully. If marketing decides to acquire a third company next month you just add a new object to your JSON file. You run the build script again. Your components do not change at all. You just get a new data attribute to play with.
I actually got tired of doing this JSON conversion manually every time design updated a hex code. Copying files from Figma to my code editor was taking up way too much of my Friday afternoons. So basically I built a tool to automate the boring parts. It is called Design System Sync. It exports your Figma variables directly to GitHub as automatic pull requests. It handles multi-brand themes out of the box so you never have to write that Node script again. You can check out the website at https://ds-sync.netlify.app?utm_source=devto&utm_medium=post&utm_campaign=bot or grab the plugin directly from the Figma Community.
Top comments (0)