Skip to content

CSS Modules

CSS Modules let you write regular CSS while automatically scoping class names to the component that imports them, avoiding the global naming collisions that plain CSS can cause. This lesson covers using them in Next.js.

Scoped Styles with .module.css Files

Any file named with the .module.css suffix is treated as a CSS Module by Next.js's build system. When you import it, you get a JavaScript object mapping each class name you wrote to a uniquely-generated class name, guaranteeing that styles never leak between components even if two components happen to use the same class name in their source.

This works with zero configuration — no extra dependency to install, no build tool setup — because Next.js's bundler handles the transformation automatically.

/* Button.module.css */
.button {
  background: blue;
  color: white;
}

// Button.tsx
import styles from "./Button.module.css";

export default function Button({ children }: { children: React.ReactNode }) {
  return <button className={styles.button}>{children}</button>;
}

The rendered class name is something like Button_button__a1b2c, guaranteed unique across the whole app.

Importing and Using Module Classes

import styles from "./Component.module.css";

<div className={styles.wrapper}>
  <p className={styles.text}>Hello</p>
</div>
  • The imported styles object's keys exactly match the class names defined in the CSS file.
  • Class names with hyphens (like .my-class) must be accessed with bracket syntax: styles["my-class"].
  • CSS Modules work in both Server and Client Components — no "use client" needed just for styling.
  • Sass syntax also works in CSS Modules by naming the file *.module.scss.

CSS Modules Cheat Sheet

Conventions and common patterns.

Task Approach
Scope styles to one component Name the file *.module.css
Access a hyphenated class styles["my-class-name"]
Combine multiple classes conditionally Use a helper like clsx or template literals
Share variables between modules CSS custom properties or Sass @use
Global styles alongside modules Keep them in a separate, non-.module CSS file

Combining Classes Conditionally

Applying more than one class, or toggling a class based on a prop, is common enough that most projects reach for a small utility like clsx or classnames rather than manually concatenating strings.

import clsx from "clsx";
import styles from "./Button.module.css";

export default function Button({
  variant,
}: {
  variant: "primary" | "secondary";
}) {
  return (
    <button
      className={clsx(styles.button, variant === "primary" && styles.primary)}
    >
      Click me
    </button>
  );
}

Composing Classes Within a Module

CSS Modules support a composes keyword to build one class on top of another, defined within the same file or imported from another module, letting you build small, reusable style building blocks.

/* Button.module.css */
.base {
  padding: 8px 16px;
  border-radius: 4px;
}

.primary {
  composes: base;
  background: blue;
}

Common Mistakes

  • Naming a file Button.css when scoped behavior was actually intended.
  • Trying to reference a hyphenated class with dot notation instead of bracket notation.
  • Manually concatenating class name strings instead of using a small utility like clsx.
  • Expecting CSS Modules class names to be predictable/stable across builds for styling from outside the component.

Key Takeaways

  • Files named *.module.css are automatically scoped, with zero extra configuration.
  • Imported class names are unique, preventing collisions across components.
  • CSS Modules work in both Server and Client Components.
  • Use a utility like clsx for conditionally combining multiple classes.
  • composes lets you build classes on top of other classes within the same module system.

Pro Tip

Reach for CSS Modules specifically for components with enough unique styling logic that Tailwind's utility classes would become unwieldy — for everything else, a utility-first approach like Tailwind often results in less code to maintain overall.