CSS

Why This Site Uses CSS Modules

Once a stylesheet grows past a certain size, you stop being able to tell which rule is affecting what. Names collide, nothing is safe to delete, and !important starts creeping in. Tailwind is one answer to that. This site uses CSS Modules instead, and here's why.

What it actually does

CSS Modules rewrites your class names at build time so each one is unique per file. You write .button, and what ships is something like Button_button__x7f2a.

Another component can use .button too and nothing collides. The real win is that you stop maintaining a naming convention by hand.

Writing one

Create Button.module.css:

.button { padding: 0.75rem 1.5rem; font-weight: 500; color: #fff; background: #0b57d0; border: none; border-radius: 999px; cursor: pointer; transition: background-color 0.2s ease; } .button:hover { background: #0a4fbb; }

Then reference it through styles:

import styles from './Button.module.css'; export default function Button({ text }: { text: string }) { return <button className={styles.button}>{text}</button>; }

styles.button is just a string. Reference a class that doesn't exist and you get undefined rather than an error, so typos fail silently. Generating type definitions closes that gap.

Why I stayed with it

Mostly because CSS stays CSS. @keyframes, ::before, media queries, :has() — the syntax you already know works unchanged, with no translation into a utility vocabulary.

The markup staying readable matters too. className={styles.card} is one token, so when you open a component the structure is the first thing you see rather than the last.

Where it falls short

It doesn't help when you need spacing or sizing to agree across components. Scoping is the whole point, so shared values have to live somewhere else.

Here they live as custom properties in globals.css, and each module pulls them in with var(--md-shape-md). Scoping styles and sharing values turn out to be two separate problems, and CSS Modules only solves the first one.