CSS Tutorial 0/203 lessons ~6 min read Lesson 122

    CSS Modules

    css modules css modules scope class names at build time — import styles from './button.module.css' yields hashed selectors like button_primary_x7f2a

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

    Introduction

    CSS Modules scope class names at build time — import styles from './Button.module.css' yields hashed selectors like Button_primary_x7f2a so styles never leak globally. Staff engineers choose CSS Modules when teams want real CSS syntax with component isolation without runtime CSS-in-JS cost.

    CSS Modules are the pragmatic middle ground between global BEM and runtime styled-components — zero JS bytes for styling on the client.

    Business problem

    Business pressure: React apps with global CSS import order bugs — third-party widget loads after app CSS and wins cascade. CSS Modules localize failure to one component import.

    • Isolation: Hash suffix prevents accidental global collisions.
    • Performance: No runtime style injection — static CSS file cacheable at CDN.
    • Migration: Incremental adoption — one component at a time.

    Why this feature exists

    Platform history: Glen Maddern and colleagues formalized CSS Modules circa 2015 as webpack css-loader feature — addressing React ecosystem global CSS pain.

    • Problem solved: Global namespace pollution in component frameworks.
    • Rejected alternative: Inline styles only — no pseudo-classes, media queries, or keyframes cleanly.
    • Modern role: Vite, Next.js App Router, CRA — default or optional CSS Modules support.

    Browser rendering perspective

    Rendering impact: Identical to plain CSS after build — hashed class strings are normal class selectors. No JS execution required for styles to apply.

    • SSR: Next.js extracts critical CSS; hashed classes in SSR HTML match client bundle.

    Internal browser workflow

    Workflow: Author .module.css → bundler scopes selectors → JS imports map local names → apply className={styles.root}.

    Feature deep dive

    Local scope by default:global(.legacy) escapes for third-party integration.

    javascript
    /* Button.module.css */
    .root { padding: 0.5rem 1rem; border-radius: 6px; }
    .primary { background: var(--primary); color: #fff; }
    /* Button.tsx */
    import styles from './Button.module.css';
    export function Button({ primary }) {
    return <button className={primary ? styles.primary : styles.root} />;
    }

    Syntax

    Composition: composes: root from './base.module.css' — merge classes at build time. :global for unscoped rules inside module file.

    Examples

    Global escape for body scroll lock:

    css
    /* Modal.module.css */
    :global(body.modal-open) { overflow: hidden; }
    .backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.5); }

    Real-world use

    Create React App default styling. Many Next.js apps use CSS Modules for leaf components while Tailwind handles layout. GitHub uses CSS Modules in parts of github.com React surfaces.

    Real production example

    Pattern: TypeScript typings via typed-css-modules; stylelint on **/*.module.css; visual regression per component story.

    Enterprise use case

    Enterprise: Hybrid stacks — DS React components with CSS Modules + token CSS variables on :root from design tokens package.

    Accessibility considerations

    A11y: Modules do not affect semantics — use correct elements. Global escape for focus traps must not break other modals.

    Performance considerations

    Performance: Static CSS — optimal for cache. Split chunks per route when using dynamic import of modules.

    SEO considerations

    SEO: SSR with CSS Modules outputs normal class attributes — no SEO penalty vs global CSS.

    Scalability considerations

    Scale: composes chains across packages need stable build graph — version token package independently.

    Common production issues

    Failures: Overusing :global recreates global cascade. Dynamic class access styles[dynamic] breaks if purge misconfigured.

    Debugging guide

    Debug: DevTools shows hashed classes — map to source via Sources panel original file. React DevTools shows className prop.

    Best practices

    • Keep :global rare and documented.
    • Share tokens via CSS variables on :root, not duplicated values.
    • One module per component file.
    • Use TypeScript definitions for class keys.

    Anti-patterns

    • Importing module styles into global index without tree-shaking plan.
    • String concatenation class names bypassing type safety.

    Trade-offs

    • Benefit: isolation, static CSS, familiar syntax.
    • Cost: no dynamic theming without variables; composes complexity.
    • vs CSS-in-JS: Modules win runtime perf; CSS-in-JS wins prop-driven dynamic styles.

    Architecture review questions

    • When is :global approved in our codebase?
    • How do tokens reach module files?

    Interview questions

    How do CSS Modules achieve scoping?(Intermediate)

    Build tool hashes class names and transforms selectors in the module file to match — local names in JS import map to hashed strings. No runtime scope injection.

    Follow-up: How do you style a third-party child component?

    Hands-on exercise

    Exercise: Convert global BEM button to CSS Module. Prove no global leakage with second conflicting .button rule.

    Staff engineer notes

    • CSS Modules + design tokens is the enterprise sweet spot for React without runtime CSS cost.

    Common pitfalls

    • :global creep — every file escapes to global.

    Try it yourself

    Edit the CSS panel — the preview updates live. Use DevTools Performance and Accessibility panels to validate.

    Try it yourself

    Preview

    Summary

    CSS Modules scope styles via build-time class hashing — delivering component isolation with standard CSS syntax and CDN-cacheable stylesheets.

    Key takeaways

    • Build-time scoped class hashes isolate component CSS.
    • Static CSS — no runtime styling JS cost.
    Ready to mark this lesson complete?Track your journey across the entire course.