What Are CSS Modules and How Do They Scope Styles?

CSS Modules are a build-time tool that automatically scopes CSS class and animation names locally to the specific JavaScript component that imports them. This article explains what CSS Modules are, how build tools compile them to generate unique identifiers, and how this mechanism prevents global namespace collisions across modern JavaScript applications.

What Are CSS Modules?

A CSS Module is a standard CSS file with a .module.css extension (by default in most build setups). Unlike traditional stylesheets that inject rules into the global cascading namespace, CSS Modules treat every class name and animation name as a local identifier by default.

CSS Modules are not a browser specification or a separate CSS syntax. Instead, they are processed during the build step by bundlers like Webpack, Vite, or Next.js using tools like postcss-modules or css-loader.

How CSS Modules Scope Styles in JavaScript

When you import a CSS Module into a JavaScript file, the build system performs two primary tasks: compiling the stylesheet with unique class names and exporting an object mapping those names to the JavaScript runtime.

1. Generating Unique Class Names

During the build process, the bundler transforms human-readable class names into globally unique strings. These unique names typically combine the file name, the original class name, and a content-based hash.

Given a CSS file named Button.module.css:

.button {
  background-color: #0070f3;
  color: #ffffff;
  padding: 8px 16px;
}

The build tool compiles the rule into something like:

.Button_button__x7z8a {
  background-color: #0070f3;
  color: #ffffff;
  padding: 8px 16px;
}

2. Exporting a JavaScript Mapping Object

When the CSS file is imported into a JavaScript or TypeScript file, the module does not return raw CSS text. Instead, it returns a JavaScript object where the keys are the original class names and the values are the generated unique strings:

import styles from './Button.module.css';

console.log(styles);
// Output: { button: "Button_button__x7z8a" }

3. Applying Classes to the DOM

You apply styles by referencing properties on the imported object. Because the generated string is injected into the DOM elements and matches the compiled CSS output, the styles apply seamlessly without affecting other elements on the page.

In a framework like React:

import styles from './Button.module.css';

export function Button({ label }) {
  return <button className={styles.button}>{label}</button>;
}

When rendered to the browser, the resulting HTML contains the unique class name:

<button class="Button_button__x7z8a">Submit</button>

Global Selectors in CSS Modules

While local scoping is the default, CSS Modules provide the :global pseudo-selector for cases where global styling is required. Styles wrapped in :global remain unchanged during the compilation process:

:global(.theme-dark) .button {
  background-color: #222222;
}

In this case, .theme-dark remains globally accessible, while .button is transformed into its scoped hash.

Why Use CSS Modules?