Frontend Development5 min read

CSS Modules & Scoped Styling

Scope CSS to components with CSS Modules: naming conventions, composition, TypeScript integration, and combining with global styles.

csscss-modulesstylingfrontend

CSS Modules transform class names to unique identifiers at build time, giving you locally-scoped styles by default. Unlike CSS-in-JS, CSS Modules have zero runtime overhead and work with standard CSS syntax, making them ideal for performance-sensitive applications.

CSS Modules are the right default for most component libraries and apps: zero runtime overhead, standard CSS syntax, works with all CSS preprocessors, and TypeScript-friendly. Use them with camelCase class names for JS access.

Workflow

  1. Create and import CSS Modules filesThe `.module.css` extension tells the bundler to scope all class names.
  2. Compose and combine class namesComponents often need multiple conditional class names; a utility handles this cleanly.
  3. TypeScript integrationTypeScript can catch typos in CSS class name references at compile time.
  4. Handle animations and global stylesAnimations defined in CSS Modules are also scoped - you need to declare them correctly.
  5. Structure CSS Modules at scaleConsistent patterns prevent duplication and make maintenance easier.

Output Format

Co-located `.module.css` files per component, `clsx` for conditional classes, TypeScript typings, global styles in separate `global.css`, design tokens as CSS custom properties.

Examples

Component: `import styles from "./Card.module.css"; export function Card({ featured }: Props) { return <div className={clsx(styles.card, { [styles.featured]: featured })}><h2 className={styles.title}>{title}</h2></div> }`
Composes: `.primaryButton { composes: button; background: var(--color-primary); color: white; } .secondaryButton { composes: button; background: transparent; border: 1px solid var(--color-primary); }`
TypeScript: `// vite-env.d.ts: declare module "*.module.css" { const classes: { readonly [key: string]: string }; export default classes; }`

Edge Cases & Gotchas

  • SSR: Next.js and Remix both support CSS Modules with SSR out of the box - styles are extracted at build time; no runtime injection needed
  • Dynamic class names: avoid string interpolation like `styles[`color-${variant}`]` - bundlers may not track these and will tree-shake the class. Use explicit conditionals instead
  • Third-party component styling: you cannot CSS Module a class that a third-party library generates. Use `:global(.third-party-class)` inside your module to target it

When to Break These Rules

  • For design systems with complex theming (multiple themes, dark mode, dynamic user themes), CSS-in-JS (styled-components, Emotion) with theme providers gives more flexibility despite the runtime cost.

Reference Files

  • CSS Modules spec: https://github.com/css-modules/css-modules
  • Vite CSS Modules: https://vitejs.dev/guide/features#css-modules
  • typed-css-modules: https://github.com/Quramy/typed-css-modules
  • clsx: https://github.com/lukeed/clsx

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.