How to scale CSS in micro frontends (without losing your mind)

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.

How To Scale CSS In Micro Frontends (Without Losing Your Mind)

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.

Why CSS is hard to scale in micro frontends

Before diving into solutions, it’s helpful to understand where the challenges come from. Some are listed below:

Team autonomy leads to fragmentation

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.

Global scope causes style collisions

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.

How to use design tokens to maintain consistency

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.

Define your tokens

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.

Transform tokens with style dictionary

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.

Use tokens in your apps

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.

How to prevent style leak across boundaries

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.

Use CSS modules to scope styling

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.

Use shadow dom to scope styling

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:

  • Styles defined inside the shadow tree apply only to that component.
  • Global styles from the host page cannot override them.
  • Even if two components share the same class name, their styles will never collide.

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 &lt;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 &lt;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.

Conclusion

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!

The post How to scale CSS in micro frontends (without losing your mind) appeared first on LogRocket Blog.

 

This post first appeared on Read More