Importing CSS Modules in React with TypeScript and Webpack: The Definitive Guide

To successfully import CSS Modules in a TypeScript-React application using Webpack, use the .module.css file naming convention and configure css-loader with mode: 'local' alongside getCSSModuleLocalIdent from react-dev-utils, ensuring deterministic class generation and component-scoped styling as demonstrated in the facebook/react repository.

Managing component-specific styles in modern React applications requires strict scoping to avoid global CSS conflicts. When importing CSS Modules into TypeScript projects bundled with Webpack, the facebook/react repository provides a reference implementation that guarantees deterministic class names and optimal production builds. This configuration leverages specific webpack rules and the react-dev-utils package to create a drop-in solution for style encapsulation.

Adopting the Standard File Naming Convention

The first step when importing CSS Modules involves following a strict file naming pattern. According to the React repository's Flight fixture configuration in fixtures/flight/config/webpack.config.js (lines 73-76), webpack automatically enables CSS Modules processing for files matching the \.module\.css$ regular expression.

Name your stylesheet files with the .module.css extension (or .module.scss/.module.sass for preprocessors). This convention allows webpack to distinguish between standard global CSS files and component-scoped modules, applying the appropriate loader configuration without additional import syntax changes.

Configuring Webpack for CSS Module Processing

The webpack configuration requires separate rules for CSS Modules and standard CSS files. In fixtures/flight/config/webpack.config.js (lines 88-99), the React team defines a specific module rule that processes .module.css files with specialized options.

Enabling the CSS Modules Loader

Configure css-loader with the modules option set to mode: 'local' and provide the getLocalIdent function. This setup generates locally scoped class names while maintaining readability:

{
  test: /\.module\.css$/,
  use: getStyleLoaders({
    importLoaders: 1,
    modules: {
      mode: 'local',
      getLocalIdent: getCSSModuleLocalIdent,
    },
  }),
}

The getCSSModuleLocalIdent helper, imported at line 19 of the same configuration file from react-dev-utils, produces deterministic class names like ComponentName_className__hash, ensuring consistent identifiers across builds.

Handling Development and Production Environments

The configuration dynamically selects between style-loader and MiniCssExtractPlugin.loader based on the environment. During development, style-loader injects styles directly into the DOM for hot module reloading support. For production builds, MiniCssExtractPlugin extracts CSS into separate files for optimal caching and parallel loading.

TypeScript Configuration Requirements

No additional TypeScript configuration is necessary beyond standard React project settings. The tsconfig.json file referenced in the Flight fixture (fixtures/flight-parcel/tsconfig.json) demonstrates that the compiler treats .module.css imports as any type by default, requiring only standard JSX and module resolution settings:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "jsx": "react-jsx",
    "strict": true,
    "esModuleInterop": true,
    "moduleResolution": "node"
  }
}

Real-World Implementation: Importing and Using CSS Modules

Once configured, import CSS Modules as JavaScript objects and reference their properties as class names. The React repository's view-transition fixture in fixtures/view-transition/src/components/Page.js (line 21) demonstrates this pattern:

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

// Usage in JSX
<div className={styles.enterSlideRight}>Content</div>

For TypeScript React components, the pattern remains identical:

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

export const Button: React.FC<{onClick?: () => void}> = ({onClick, children}) => (
  <button className={styles.root} onClick={onClick}>
    {children}
  </button>
);

With the corresponding Button.module.css:

.root {
  background: #0069d9;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  cursor: pointer;
}

Complete Webpack Configuration Reference

Below is the essential webpack configuration excerpt adapted from fixtures/flight/config/webpack.config.js, demonstrating the complete setup for importing CSS Modules:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');

module.exports = (env) => {
  const isDev = env === 'development';

  const getStyleLoaders = (cssOptions, preProcessor) => [
    isDev && require.resolve('style-loader'),
    {
      loader: MiniCssExtractPlugin.loader,
      options: {}
    },
    {
      loader: require.resolve('css-loader'),
      options: cssOptions,
    },
    preProcessor && require.resolve(preProcessor),
  ].filter(Boolean);

  return {
    module: {
      rules: [
        // CSS Modules rule
        {
          test: /\.module\.css$/,
          use: getStyleLoaders({
            importLoaders: 1,
            modules: {
              mode: 'local',
              getLocalIdent: getCSSModuleLocalIdent,
            },
          }),
        },
        // Standard CSS rule
        {
          test: /\.css$/,
          exclude: /\.module\.css$/,
          use: getStyleLoaders({ importLoaders: 1, modules: { mode: 'icss' } }),
        },
      ],
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: isDev ? '[name].css' : '[name].[contenthash].css',
      }),
    ],
  };
};

Summary

  • File naming: Use .module.css extensions to trigger CSS Modules processing in webpack, as defined in fixtures/flight/config/webpack.config.js (lines 73-76).
  • Webpack configuration: Configure css-loader with modules: { mode: 'local', getLocalIdent: getCSSModuleLocalIdent } to enable local scoping and deterministic class generation (lines 88-99).
  • Class name generation: Import getCSSModuleLocalIdent from react-dev-utils to produce readable, hash-suffixed class names like ComponentName_class__abc123.
  • Environment handling: Use style-loader for development hot reloading and MiniCssExtractPlugin for production extraction.
  • TypeScript support: Standard tsconfig.json settings suffice; no additional type declarations are required for basic CSS Modules functionality.

Frequently Asked Questions

Do I need to install react-dev-utils to use CSS Modules with Webpack?

While you can write a custom getLocalIdent function, using react-dev-utils provides a battle-tested implementation that mirrors Create React App's behavior. According to the facebook/react source code, this helper at scripts/react-dev-utils/getCSSModuleLocalIdent.js ensures deterministic class names across builds and maintains naming conventions that aid in debugging.

Can I use CSS Modules with Sass or Less in React TypeScript projects?

Yes. The webpack configuration supports .module.scss and .module.sass files by adding the appropriate preprocessor loader to the getStyleLoaders array. The CSS Modules processing occurs before Sass compilation, allowing you to use nested selectors and variables while maintaining component-scoped output.

How do I handle TypeScript type definitions for CSS Modules?

By default, TypeScript treats CSS Module imports as any. For stricter type checking, you can create a global.d.ts file declaring module types for *.module.css files, mapping them to objects with string properties. Alternatively, use the typescript-plugin-css-modules or similar tooling to generate type definitions automatically from your CSS files.

What is the difference between CSS Modules and CSS-in-JS solutions?

CSS Modules provide build-time scoping through webpack loaders, generating unique class names during compilation and outputting standard CSS files. CSS-in-JS solutions like styled-components or emotion generate styles at runtime or during server-side rendering, often injecting styles directly into the DOM. The approach shown in fixtures/flight/config/webpack.config.js represents the build-time optimization strategy, offering better performance for static sites and zero runtime overhead.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →