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.
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.
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.
You now understand CSS Modules. Next, look at global CSS — the simplest way to apply site-wide styles.