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.