React Tutorial 0/120 lessons ~6 min read Lesson 28

    CSS Modules

    CSS Modules in React: locally scoped .module.css styles, styles.className, conditional and dynamic classes, :global, and comparison with other styling approaches.

    Course progress0%
    Focus
    38 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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

    1. What CSS Modules are and how .module.css scoping works
    2. Importing modules and applying styles.className
    3. Multiple, conditional, and dynamic class names
    4. Pseudo-classes, media queries, variables, and :global
    5. How CSS Modules compare to regular CSS, inline styles, CSS-in-JS, and utilities
    6. Common mistakes, best practices, and interview questions

    What Are CSS Modules?

    A CSS Module is a CSS file whose filename follows:

    text
    ComponentName.module.css

    For example: Button.module.css

    When imported into a React component, its class names become available through a JavaScript object.

    text
    Button.jsx
    Button.module.css

    Button.module.css

    css
    .button {
    background-color: blue;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 6px;
    }

    Button.jsx

    tsx
    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:

    css
    .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

    tsx
    // CSS: .button { background-color: blue; }
    <button className="button">Save</button>

    The class name is global.

    CSS Modules

    tsx
    // 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:

    text
    Button.module.css
    Card.module.css
    Navbar.module.css
    Profile.module.css
    Dashboard.module.css
    text
    src/
    ├── components/
    │ ├── Button.jsx
    │ ├── Button.module.css
    │ ├── Card.jsx
    │ └── Card.module.css

    Creating a CSS Module

    Card.module.css

    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

    tsx
    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:

    tsx
    <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:

    html
    <div class="Card_card_x7k92">
    text
    .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:

    text
    Button.module.css .title → Button_title_x1a2
    Card.module.css .title → Card_title_b7c8

    Importing CSS Modules

    tsx
    import styles from "./Component.module.css";
    // then
    className={styles.className}
    import styles from "./Profile.module.css";
    function Profile() {
    return (
    <div className={styles.profile}>
    <h2 className={styles.name}>John</h2>
    </div>
    );
    }

    Multiple Classes

    css
    .button {
    padding: 10px;
    }
    .primary {
    background-color: blue;
    }
    .large {
    font-size: 18px;
    }
    tsx
    <button
    className={`${styles.button} ${styles.primary} ${styles.large}`}
    >
    Save
    </button>

    Combining CSS Modules with Conditional Classes

    tsx
    function Button({ primary }) {
    return (
    <button
    className={`${styles.button} ${
    primary ? styles.primary : styles.secondary
    }`}
    >
    Click Me
    </button>
    );
    }
    css
    .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:

    tsx
    const classes = [
    styles.button,
    primary && styles.primary,
    large && styles.large
    ]
    .filter(Boolean)
    .join(" ");
    <button className={classes}>
    Click Me
    </button>

    CSS Modules with Props

    tsx
    function Alert({ type }) {
    return (
    <div className={`${styles.alert} ${styles[type]}`}>
    Something happened.
    </div>
    );
    }
    <Alert type="success" />
    <Alert type="error" />
    css
    .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.

    tsx
    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:

    tsx
    styles["primary-button"]
    // Prefer:
    // .primaryButton { ... }
    styles.primaryButton

    CSS Modules and Pseudo-Classes

    css
    .button {
    background-color: blue;
    color: white;
    padding: 10px 20px;
    }
    .button:hover {
    background-color: darkblue;
    }
    tsx
    <button className={styles.button}>
    Hover Me
    </button>

    You do not need React state for :hover — CSS handles interaction states.

    CSS Modules and Pseudo-Elements

    css
    .title::before {
    content: "★";
    margin-right: 5px;
    }
    tsx
    <h2 className={styles.title}>
    Featured Course
    </h2>

    CSS Modules and Media Queries

    css
    .container {
    padding: 30px;
    }
    @media (max-width: 768px) {
    .container {
    padding: 15px;
    }
    }
    tsx
    <div className={styles.container}>Content</div>

    CSS Modules with Nested Selectors

    You can nest (.card h2), but explicit classes are often clearer:

    css
    .card {
    padding: 20px;
    }
    .title {
    margin-bottom: 10px;
    }
    .description {
    color: #666;
    }
    tsx
    <div className={styles.card}>
    <h2 className={styles.title}>Title</h2>
    <p className={styles.description}>Description</p>
    </div>

    CSS Modules and Child Components

    text
    Card.jsx + Card.module.css
    CardHeader.jsx + CardHeader.module.css
    CardBody.jsx + CardBody.module.css
    tsx
    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:

    text
    styles/
    ├── global.css
    ├── variables.css
    components/
    ├── 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:

    css
    :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

    css
    .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);
    }
    text
    Global design tokens + Local component styles → CSS Modules

    CSS Modules with CSS Grid

    css
    .dashboard {
    display: grid;
    grid-template-columns: 250px 1fr;
    min-height: 100vh;
    }
    .sidebar {
    padding: 20px;
    }
    .content {
    padding: 30px;
    }
    tsx
    <div className={styles.dashboard}>
    <aside className={styles.sidebar}>Sidebar</aside>
    <main className={styles.content}>Dashboard</main>
    </div>

    Building a Reusable Button

    css
    .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; }
    tsx
    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

    FeatureRegular CSSCSS Modules
    ScopeGlobalLocal by default
    Naming conflictsPossibleGreatly reduced
    ImportOften globalRequired
    Class namesStringsstyles.className
    Component isolationManualBuilt in
    Best forGlobal stylesComponent 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

    tsx
    // 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

    tsx
    // Incorrect
    <button className="button">
    // Correct
    <button className={styles.button}>

    3. Forgetting the import

    tsx
    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

    1. Keep styles close to components (Button/Button.jsx + Button.module.css)
    2. Prefer meaningful class names (.button, .errorMessage)
    3. Use CSS for hover, focus, responsive layouts, animations
    4. Use props for variants (<Button variant="danger" />)
    5. Keep global CSS small (resets, typography, variables, app-wide rules)

    CSS Modules Mental Model

    text
    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?

    text
    Button.module.css

    3. How do you import?

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

    4. How do you apply a class?

    tsx
    <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?

    tsx
    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

    1. Card module — container, title, description, button, hover
    2. Button — primary / secondary / danger variants
    3. Sizes — small / medium / large via props
    4. Profile — avatar, name, role, description, action button
    5. Responsive card grid — 3 / 2 / 1 columns with CSS Modules + Grid

    Quick Cheat Sheet

    TaskPattern
    CreateButton.module.css
    Importimport styles from "./Button.module.css"
    ApplyclassName={styles.button}
    Multiple`${styles.a} ${styles.b}`
    Conditionalactive ? styles.active : styles.inactive
    Dynamicstyles[variant]
    Hyphenatedstyles["primary-button"]
    Global selector:global(.dark) .card

    Key Takeaway

    CSS Modules bridge traditional CSS and component-based React— each component can own its styles:

    text
    Button/ → Button.jsx + Button.module.css
    Card/ → Card.jsx + Card.module.css
    Profile/ → Profile.jsx + Profile.module.css
    tsx
    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.

    Ready to mark this lesson complete?Track your journey across the entire course.