The micro frontend architecture is a powerful concept that lets teams build different parts of a frontend application as independent and standalone pieces. A developer might build Feature-A using React and Tailwind, while another works on Feature-B with Vue and CSS-in-JS, providing great autonomy.
However, one major issue that most developers face, regardless of their micro frontend stack, is the difficulty in scaling CSS across all these pieces. Teams often encounter issues such as conflicting styles, redundant code, and bloated bundles.
In this article, we’ll explore practical ways to scale CSS in a micro-frontend setup. We’ll cover how to utilize tools like Style Dictionary to create shared design tokens and how to leverage techniques such as CSS Modules and the Shadow DOM to prevent style collisions across independent components of your micro-frontend apps.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Before diving into solutions, it’s helpful to understand where the challenges come from. Some are listed below:
The main reason CSS is difficult to scale in a micro frontend architecture is that there is no standard method for managing styles across each independent project.
One team might use Sass, another might prefer Tailwind, and another could rely on styled-components or vanilla CSS. Each setup might work fine independently. However, when you bring all the parts together, you’ll start noticing issues like buttons looking different, spacing drifts, and the app no longer feels like a complete product.
Fragmentation is only part of the story. Even if all teams followed the same conventions, the language itself adds another layer of complexity.
CSS is designed to be global by default. Once a stylesheet loads in the browser, it can target any element on the page, regardless of who wrote it or where it came from. This global scoping is one major reason why CSS is hard to scale in micro frontends.
For example, imagine two teams working independently where Team A owns the Product Catalog, Team B owns the Shopping Cart, and both teams use the same class name, .button, for different components, as shown below:
Team A’s CSS:
.button {
background: #0070f3;
color: white;
padding: 12px 24px;
border-radius: 4px;
}
Team B’s CSS:
.button {
background: #ccc;
color: #333;
padding: 8px 16px;
border-radius: 2px;
}
When both of these micro frontends render on the same page, all the styles end up in the same DOM. If Team B’s stylesheet loads after Team A’s, its rules override Team A’s styles, and the Product Catalog’s buttons lose their intended color, spacing, and border radius, even though Team A didn’t change anything.
Now that you understand why CSS is difficult to scale in a micro-frontend setup, the next question is how to create shared styling rules without killing autonomy. Design tokens are a good starting point.
Design tokens are one of the most effective ways to maintain consistency in your UI across multiple frontend applications when dealing with micro frontends.
At a high level, a design token is just a variable that represents a design decision, such as colors, spacing, font sizes, border radii, and shadows. Instead of hardcoding these values across different apps, you define them once in a shared source of truth, then transform and distribute them in formats your various projects can consume.
For example, say your design system defines a primary color, a secondary color, and a base font size. The steps below explain how to apply the design token strategy.
Start by defining your tokens in a simple JSON file. For example, in a tokens.json file, as shown below.
{
"color": {
"primary": { "value": "#0070f3" },
"secondary": { "value": "#1e90ff" }
},
"font": {
"size": {
"base": { "value": "16px" }
}
}
}
This file becomes your single source of truth, the central reference point where designers and developers align on shared values, such as colors, spacing, and typography, ensuring every part of the system speaks the same visual language.
The next step is to use a token transformation tool, such as Style Dictionary, to convert your JSON tokens into different output formats, including CSS variables, SCSS variables, JavaScript constants, or even platform-specific files (iOS, Android, etc.).
Install Style Dictionary by running:
npm install style-dictionary --save-dev
Then, create a configuration file named style-dictionary.config.cjs, and paste the following content into it:
module.exports = {
source: ["tokens.json"],
platforms: {
css: {
transformGroup: "css",
buildPath: "dist/css/",
files: [
{
destination: "tokens.css",
format: "css/variables"
}
]
},
js: {
transformGroup: "js",
buildPath: "dist/js/",
files: [
{
destination: "tokens.js",
format: "javascript/es6"
}
]
}
}
};
The code above tells Style Dictionary where your token source is and how to build it for different platforms. It takes your tokens.json file and outputs ready-to-use versions like CSS variables for styling and JavaScript constants for direct use in code.
Next run:
npx style-dictionary build
After running the build command, Style Dictionary will generate files similar to the examples below.
// dist/css/tokens.css
:root {
--color-primary: #0070f3;
--color-secondary: #1e90ff;
--font-size-base: 16px;
}
dist/js/tokens.js
export const tokens = {
color: {
primary: "#0070f3",
secondary: "#1e90ff"
},
font: {
size: {
base: "16px"
}
}
};
These files contain your design tokens, converted into CSS variables and JavaScript exports, which you can use directly in your applications.
At this point, your design tokens can be used directly in any frontend app. If someone on your team is building a React app with Tailwind CSS, they can import the tokens and use them directly or extend Tailwind with them.
Tailwind config example:
// tailwind.config.js
const tokens = require('@your-org/tokens/dist/js/tokens');
module.exports = {
theme: {
extend: {
colors: {
primary: tokens.color.primary,
secondary: tokens.color.secondary
},
fontSize: {
base: tokens.font.size.base
}
}
}
};
Or even import the CSS version directly in React:
import '@your-org/tokens/dist/css/tokens.css';
export function Button() {
return <button style={{ background: 'var(--color-primary)' }}>Click</button>;
}
With this approach, all your micro frontends share a consistent visual language, eliminating the need for duplicate values. You can also package the tokens as a standalone npm module (for example, @your-org/design-tokens) so every team can install and use it like any other dependency:
npm install @your-org/design-tokens
When the design team updates a color, font, or spacing value, simply bump the version, publish a new release, and all apps can pull the changes through a dependency update.
Even with design tokens, teams can still overextend or misuse them, which can lead to the same style conflicts described earlier. Let’s look at a few best practices to prevent that.
CSS Modules are the simplest and most reliable way to prevent style collisions. They help you scope class names automatically by generating unique hashed identifiers during the build step.
Frameworks like Next.js, Vite, and Create React App support CSS Modules out of the box. You only need to name your file with the .module.css suffix, and the build tool automatically treats it as a CSS Module.
For example, here’s how it works in a file named Card.module.css:
.card {
border: 1px solid #ccc;
padding: 16px;
}
And in your React component:
import styles from './Card.module.css'
export function Card() {
return <div className={styles.card}>Hello</div>
}
When you build the project, the class name .card is transformed into something like:
.Card_card__3f2a9
This hashed class name ensures isolation because no other .card from another micro frontend can override it. Each build generates unique identifiers, effectively eliminating collisions. Also, while the browser still receives regular CSS, each micro frontend’s styles are automatically namespaced.
Another strategy for scoping styles in micro frontends is using the Shadow DOM. While it’s not as common as CSS Modules, it’s a powerful option when you’re using Web Components in your microfrontend setup.
With this approach, you can build a component as a Web Component that uses Shadow DOM internally, then import it into your React, Vue, or Angular app. When the component is rendered in the browser, it lives inside its own shadow tree, a private DOM that the browser keeps separate from the rest of the document.
That means:
Let’s take the card component below as an example:
class UserCard extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({ mode: 'open' })
shadow.innerHTML = `
<style>
.card {
border: 1px solid #ccc;
padding: 8px;
border-radius: 4px;
background: white;
}
</style>
<div class="card"><slot></slot></div>
`
}
}
customElements.define('user-card', UserCard)
When this component runs, the browser creates a shadow root attached to <user-card>. The .card selector exists only within that shadow root. Even if your main app has its own .card styles or uses a global CSS library like Tailwind, Bootstrap, or Material UI, those styles won’t affect the content inside <user-card>.
Likewise, any CSS inside the component won’t leak out to affect the rest of the app. It’s a self-contained visual sandbox that guarantees consistent styling, regardless of the environment it’s loaded into.
Combined, these techniques allow your team to remain independent and enjoy the autonomy of micro frontends without making the UI feel like a patchwork of unrelated apps.
In this article, we explored why CSS is difficult to scale in micro frontend architectures and how team autonomy often leads to inconsistent styling. You also learned about practical solutions to these challenges using design tokens, CSS Modules, and the Shadow DOM to maintain isolated and predictable styles.
As a next step, you can explore tools and practices that strengthen this foundation, such as building a shared design system or automating token updates through CI/CD. These will help you push your styling architecture even further. Thanks for reading!

Learn how ChatGPT’s new browser Atlas fits into a frontend developer’s toolkit, including the debugging and testing process.

Users don’t think in terms of frontend or backend; they just see features. This article explores why composition, not reactivity, is becoming the core organizing idea in modern UI architecture.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the November 19th issue.

Jack Herrington writes about how React 19.2 rebuilds async handling from the ground up with use(),
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now