CSS Modules
CSS Modules in React: locally scoped .module.css styles, styles.className, conditional and dynamic classes, :global, and comparison with other styling approaches.
CSS Modules
CSS Modules are a powerful way to write CSS for React components while keeping styles scoped to the component that uses them.
Instead of creating global CSS class names such as .button, .card, or .title, CSS Modules automatically generate unique class names so that styles from one component do not accidentally affect another.
They are especially useful as React applications grow because they provide component-level styling without naming conflicts.
Learning Objectives
- What CSS Modules are and how
.module.cssscoping works - Importing modules and applying
styles.className - Multiple, conditional, and dynamic class names
- Pseudo-classes, media queries, variables, and
:global - How CSS Modules compare to regular CSS, inline styles, CSS-in-JS, and utilities
- Common mistakes, best practices, and interview questions
What Are CSS Modules?
A CSS Module is a CSS file whose filename follows:
ComponentName.module.css
For example: Button.module.css
When imported into a React component, its class names become available through a JavaScript object.
Button.jsxButton.module.css
Button.module.css
.button {background-color: blue;color: white;padding: 10px 20px;border: none;border-radius: 6px;}
Button.jsx
import styles from "./Button.module.css";function Button() {return <button className={styles.button}>Click Me</button>;}export default Button;
styles.button refers to the .button class inside Button.module.css.
Why Do We Need CSS Modules?
Traditional CSS is global. If two components both define:
.button {background-color: blue;}/* elsewhere */.button {background-color: red;}
Both share the same global class name and can create unexpected styling problems. CSS Modules make class names locally scoped.
Global CSS vs CSS Modules
Traditional CSS
// CSS: .button { background-color: blue; }<button className="button">Save</button>
The class name is global.
CSS Modules
// CSS: .button { background-color: blue; }<button className={styles.button}>Save</button>
The class is scoped to the component that imports the module.
CSS Module File Naming
CSS Modules are usually identified using .module.css:
Button.module.cssCard.module.cssNavbar.module.cssProfile.module.cssDashboard.module.css
src/├── components/│ ├── Button.jsx│ ├── Button.module.css│ ├── Card.jsx│ └── Card.module.css
Creating a CSS Module
Card.module.css
.card {padding: 20px;border: 1px solid #ddd;border-radius: 8px;background-color: white;}.title {font-size: 24px;margin-bottom: 10px;}.description {color: #666;}
Card.jsx
import styles from "./Card.module.css";function Card() {return (<div className={styles.card}><h2 className={styles.title}>React Course</h2><p className={styles.description}>Learn React from the fundamentals to advanced concepts.</p></div>);}export default Card;
The key line is import styles from "./Card.module.css" — the styles object provides access to the CSS classes.
How CSS Modules Work
You write:
<div className={styles.card}>
The browser does not necessarily receive a simple class="card". The build system transforms it into a unique class name conceptually like:
<div class="Card_card_x7k92">
.card↓locally scoped class↓unique generated class name
CSS Modules Are Locally Scoped
Both Button.module.css and Card.module.css can safely contain .title without names like .button-title / .card-title:
Button.module.css .title → Button_title_x1a2Card.module.css .title → Card_title_b7c8
Importing CSS Modules
import styles from "./Component.module.css";// thenclassName={styles.className}import styles from "./Profile.module.css";function Profile() {return (<div className={styles.profile}><h2 className={styles.name}>John</h2></div>);}
Multiple Classes
.button {padding: 10px;}.primary {background-color: blue;}.large {font-size: 18px;}
<buttonclassName={`${styles.button} ${styles.primary} ${styles.large}`}>Save</button>
Combining CSS Modules with Conditional Classes
function Button({ primary }) {return (<buttonclassName={`${styles.button} ${primary ? styles.primary : styles.secondary}`}>Click Me</button>);}
.button {padding: 10px 20px;border-radius: 6px;}.primary {background-color: blue;color: white;}.secondary {background-color: gray;color: black;}
Using a Helper Function for Classes
When many conditions are involved, prefer an array:
const classes = [styles.button,primary && styles.primary,large && styles.large].filter(Boolean).join(" ");<button className={classes}>Click Me</button>
CSS Modules with Props
function Alert({ type }) {return (<div className={`${styles.alert} ${styles[type]}`}>Something happened.</div>);}<Alert type="success" /><Alert type="error" />
.alert {padding: 12px;border-radius: 6px;}.success {background-color: #d1fae5;color: #065f46;}.error {background-color: #fee2e2;color: #991b1b;}.warning {background-color: #fef3c7;color: #92400e;}
CSS Modules and Dynamic Class Names
Static access: styles.button. Dynamic access: styles[type] when type is a class name that exists in the module. Keep the value controlled and valid.
const type = "success";<div className={styles[type]} />// resolves to styles.success
Hyphenated Class Names
styles.primary-button is invalid JavaScript (hyphen as minus). Use bracket notation or camelCase class names:
styles["primary-button"]// Prefer:// .primaryButton { ... }styles.primaryButton
CSS Modules and Pseudo-Classes
.button {background-color: blue;color: white;padding: 10px 20px;}.button:hover {background-color: darkblue;}
<button className={styles.button}>Hover Me</button>
You do not need React state for :hover — CSS handles interaction states.
CSS Modules and Pseudo-Elements
.title::before {content: "★";margin-right: 5px;}
<h2 className={styles.title}>Featured Course</h2>
CSS Modules and Media Queries
.container {padding: 30px;}@media (max-width: 768px) {.container {padding: 15px;}}
<div className={styles.container}>Content</div>
CSS Modules with Nested Selectors
You can nest (.card h2), but explicit classes are often clearer:
.card {padding: 20px;}.title {margin-bottom: 10px;}.description {color: #666;}
<div className={styles.card}><h2 className={styles.title}>Title</h2><p className={styles.description}>Description</p></div>
CSS Modules and Child Components
Card.jsx + Card.module.cssCardHeader.jsx + CardHeader.module.cssCardBody.jsx + CardBody.module.css
import styles from "./CardHeader.module.css";function CardHeader() {return (<header className={styles.header}><h2 className={styles.title}>Course</h2></header>);}
CSS Modules and Global Styles
Use global CSS for application-wide rules; modules for component styles:
styles/├── global.css├── variables.csscomponents/├── Button/│ ├── Button.jsx│ └── Button.module.css├── Card/│ ├── Card.jsx│ └── Card.module.css
Global Selectors in CSS Modules
Use :global carefully when you need to target a global class:
:global(.dark) .card {background-color: #222;}
.card stays local; .dark is global. Excessive :global reduces the benefit of CSS Modules.
CSS Variables with CSS Modules
.card {--card-padding: 20px;padding: var(--card-padding);}:root {--primary-color: #2563eb;--spacing-md: 16px;}.button {background-color: var(--primary-color);padding: var(--spacing-md);}
Global design tokens + Local component styles → CSS Modules
CSS Modules with CSS Grid
.dashboard {display: grid;grid-template-columns: 250px 1fr;min-height: 100vh;}.sidebar {padding: 20px;}.content {padding: 30px;}
<div className={styles.dashboard}><aside className={styles.sidebar}>Sidebar</aside><main className={styles.content}>Dashboard</main></div>
Building a Reusable Button
.button {border: none;border-radius: 6px;padding: 10px 18px;font-size: 16px;cursor: pointer;}.primary { background-color: #2563eb; color: white; }.secondary { background-color: #e5e7eb; color: #111827; }.danger { background-color: #dc2626; color: white; }.small { padding: 6px 12px; font-size: 14px; }.large { padding: 14px 24px; font-size: 18px; }
import styles from "./Button.module.css";function Button({variant = "primary",size = "medium",children}) {const classNames = [styles.button,styles[variant],size !== "medium" && styles[size]].filter(Boolean).join(" ");return (<button className={classNames}>{children}</button>);}export default Button;// Usage<Button>Save</Button><Button variant="secondary">Cancel</Button><Button variant="danger" size="large">Delete</Button>
CSS Modules vs Regular CSS
| Feature | Regular CSS | CSS Modules |
|---|---|---|
| Scope | Global | Local by default |
| Naming conflicts | Possible | Greatly reduced |
| Import | Often global | Required |
| Class names | Strings | styles.className |
| Component isolation | Manual | Built in |
| Best for | Global styles | Component styles |
CSS Modules vs Inline Styles
CSS Modules support :hover, media queries, pseudo-elements, animations, and complex selectors. Inline styles are better for simple dynamic values.
CSS Modules vs CSS-in-JS
CSS Modules let you keep writing familiar CSS with component-level scoping — easier for teams already comfortable with CSS.
CSS Modules vs Utility Classes
// Utility-first<button className="px-4 py-2 rounded bg-blue-600 text-white">Save</button>// CSS Modules<button className={styles.button}>Save</button>// Can combine<div className={`${styles.card} flex items-center`}>
Neither is universally better — establish consistent project conventions.
Common Mistakes
1. Forgetting .module
Button.css is not automatically a CSS Module — use Button.module.css.
2. Using a string instead of styles
// Incorrect<button className="button">// Correct<button className={styles.button}>
3. Forgetting the import
import styles from "./Button.module.css";
4. Invalid JavaScript property names
Prefer styles.primaryButton or styles["primary-button"].
5. Making everything global
Heavy :global(...) use reduces the value of CSS Modules.
6. One huge module file
Avoid everything.module.css — organize styles around components.
CSS Modules Best Practices
- Keep styles close to components (
Button/Button.jsx+Button.module.css) - Prefer meaningful class names (
.button,.errorMessage) - Use CSS for hover, focus, responsive layouts, animations
- Use props for variants (
<Button variant="danger" />) - Keep global CSS small (resets, typography, variables, app-wide rules)
CSS Modules Mental Model
CSS Module│▼Local CSS class names│▼Build-time transformation│▼Unique class names│▼No accidental collisions
CSS Modules turn ordinary CSS classes into component-scoped styles.
When Should You Use CSS Modules?
- Many components and growing class namespaces
- You want to keep writing traditional CSS
- You want to avoid class-name collisions
- Components should own their styles
- You need full CSS features without CSS-in-JS
Especially useful for medium and large React apps where global CSS becomes hard to manage.
Interview Questions
1. What are CSS Modules?
CSS files whose class names are locally scoped to the importing component.
2. How do you create one?
Button.module.css
3. How do you import?
import styles from "./Button.module.css";
4. How do you apply a class?
<button className={styles.button}>
5. What problem do they solve?
Global CSS class-name collisions via locally scoped names.
6. Can they use pseudo-classes?
Yes — e.g. .button:hover.
7. Can they use media queries?
Yes.
8. Hyphenated class access?
styles["primary-button"]
9. Modules + global CSS together?
Yes — global for app-wide rules; modules for components.
10. Are CSS Modules a React feature?
Not exactly — they are a CSS/build-tool convention supported by React project tooling.
Practice Exercises
- Card module — container, title, description, button, hover
- Button — primary / secondary / danger variants
- Sizes — small / medium / large via props
- Profile — avatar, name, role, description, action button
- Responsive card grid — 3 / 2 / 1 columns with CSS Modules + Grid
Quick Cheat Sheet
| Task | Pattern |
|---|---|
| Create | Button.module.css |
| Import | import styles from "./Button.module.css" |
| Apply | className={styles.button} |
| Multiple | `${styles.a} ${styles.b}` |
| Conditional | active ? styles.active : styles.inactive |
| Dynamic | styles[variant] |
| Hyphenated | styles["primary-button"] |
| Global selector | :global(.dark) .card |
Key Takeaway
CSS Modules bridge traditional CSS and component-based React— each component can own its styles:
Button/ → Button.jsx + Button.module.cssCard/ → Card.jsx + Card.module.cssProfile/ → Profile.jsx + Profile.module.css
import styles from "./Button.module.css";<button className={styles.button}>Save</button>
You get local styling, fewer naming conflicts, reusable components, and the full power of CSS while keeping React code organized and maintainable.