Skip to content
C.W.K.
Stream
Lesson 01 of 08 · published

CSS Modules

~18 min · CSS Modules, scoping, no conflicts

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

Class names without the cascade tax

CSS Modules scope every class name to the file that imports them. The build hashes class names so .button in Card.module.css can't collide with .button elsewhere. Pure CSS at runtime — no JS, no hooks, works in Server Components.

How to use them

Name your file *.module.css, import as a default JS object, and read class names from it: className={styles.button}. Plain CSS rules inside; composes: lets one class inherit from another.

Why they still earn their keep

Tailwind handles 80% of styling needs in modern apps, but CSS Modules win for design systems with intricate component-internal styling, animations, and pseudo-element tricks where utility classes would explode.

Use CSS Modules for component-owned styles that benefit from ordinary CSS, local class names, media queries, and pseudo-selectors without a client runtime. Keep design tokens in shared custom properties and compose small semantic classes instead of duplicating declarations.

Scoping prevents class-name collisions; it does not prevent inconsistent spacing, inaccessible contrast, or an accidental dependency on DOM structure. A local stylesheet still needs shared tokens and a deliberate cascade. Render two components that both define .button, inspect their generated class names, and verify neither leaks. Then test focus, hover, reduced motion, narrow width, and dark mode while confirming the production build ships no styling JavaScript.

Code

Button.module.css·css
.button {
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  background: #2563eb;
  color: white;
  border: none;
  cursor: pointer;
  transition: background-color 120ms ease;
}

.button:hover {
  background: #1d4ed8;
}

.primary {
  composes: button;
  font-weight: 600;
}

.danger {
  composes: button;
  background: #dc2626;
}

.danger:hover {
  background: #b91c1c;
}
Using the module·tsx
import styles from './Button.module.css';

export function PrimaryButton({ children }: { children: React.ReactNode }) {
  return <button className={styles.primary}>{children}</button>;
}

export function DangerButton({ children }: { children: React.ReactNode }) {
  return <button className={styles.danger}>{children}</button>;
}
// Built class name: 'Button_primary__a3b2c' — globally unique, always.

External links

Exercise

Build a button component with three variants (primary, secondary, danger) using CSS Modules and composes:. Confirm the rendered class names in DevTools are hashed and conflict-free.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.