CSS Modules

Scoped CSS with automatic class names

CSS Modules

CSS Modules give you locally scoped class names, eliminating naming collisions. Packem handles them through @visulima/rollup-plugin-css and can generate scoped names automatically for *.module.css files.

Overview

This example demonstrates:

  • Enabling CSS Modules for *.module.* files via autoModules
  • Customizing scoped name generation
  • Importing scoped class names into TypeScript
  • Generating companion .d.ts files for type-safe class names

Setup

CSS Modules build on PostCSS, so install postcss:

npm install --save-dev postcss

Directory Structure

my-package/
├── src/
│   ├── Button.ts
│   └── Button.module.css
├── package.json
└── packem.config.ts

Source Files

src/Button.module.css

.button {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 0.25rem;
  cursor: pointer;
}

.primary {
  background-color: #007bff;
  color: white;
}

.secondary {
  background-color: #6c757d;
  color: white;
}

src/Button.ts

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

export function createButton(variant: 'primary' | 'secondary' = 'primary') {
  const button = document.createElement('button')
  button.className = `${styles.button} ${styles[variant]}`
  button.textContent = 'Click me'
  return button
}

Configuration

Enable CSS Modules with the autoModules option. It accepts a boolean, a regular expression, or a function. When true, it applies to files named [name].module.[ext] (for example Button.module.css).

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'
import postcssLoader from '@visulima/packem/css/loader/postcss'

export default defineConfig({
  transformer,
  rollup: {
    css: {
      mode: 'extract',
      loaders: [postcssLoader],
      autoModules: true,
    },
  },
})

To restrict CSS Modules to a custom pattern, pass a regular expression:

css: {
  loaders: [postcssLoader],
  autoModules: /\.module\./,
}

Customizing Scoped Names

Module behavior is configured through postcss.modules. The generateScopedName placeholder controls the emitted class names (default "[name]_[local]_[hash:8]").

export default defineConfig({
  transformer,
  rollup: {
    css: {
      mode: 'extract',
      loaders: [postcssLoader],
      postcss: {
        modules: {
          generateScopedName: '[name]__[local]___[hash:base64:5]',
          exportGlobals: true,
        },
      },
    },
  },
})

Available modules options:

  • generateScopedName — placeholder string or function for scoped name generation
  • mode — default scoping mode ("local", default)
  • exportGlobals — export global class names
  • failOnWrongOrder — fail on wrong composition order
  • include — files to treat as CSS Modules

You can also enable modules through postcss.modules directly (set it to true or an options object) instead of using autoModules.

Type-Safe Class Names

Set the dts option to generate companion .d.ts files alongside your CSS Modules, giving you IntelliSense and compile-time checks for class names.

css: {
  mode: 'extract',
  loaders: [postcssLoader],
  autoModules: true,
  dts: true,
}

This emits a declaration file next to each module:

// src/Button.module.css.d.ts (auto-generated)
declare const button: string
declare const primary: string
declare const secondary: string

interface ModulesExports {
  button: string
  primary: string
  secondary: string
}

declare const styles: ModulesExports
export default styles
export { button, primary, secondary }

Named Exports

Enable namedExports to export each class as a named binding alongside the default export:

css: {
  loaders: [postcssLoader],
  autoModules: true,
  namedExports: true,
}

Building

packem build
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues