Why? Improves maintainability and prevents style collisions.
When styles live beside the component they dress, you can rename, move, or delete UI with confidence—nothing else breaks. Local scope avoids specificity wars, improves tree-shaking, and keeps global CSS tiny, which accelerates first paint and simplifies theming.
// features/profile/ProfileCard.tsx
import styles from "./ProfileCard.module.css";
type Props = { name: string; avatarUrl: string };
export function ProfileCard({ name, avatarUrl }: Props) {
return (
<article className={styles.card}>
<img src={avatarUrl} alt={`${name} avatar`} className={styles.avatar} />
<h2 className={styles.name}>{name}</h2>
</article>
);
}
/* ProfileCard.module.css (same folder)
.card { @apply flex items-center gap-4 rounded-lg bg-white p-4 shadow; }
.avatar { @apply h-12 w-12 rounded-full object-cover; }
.name { @apply text-lg font-semibold text-zinc-900; }
*/
Why it’s good:
@apply
with Tailwind keeps file small and predictable.// uses a global class defined in app.css
export function ProfileCard({ name, avatarUrl }: { name: string; avatarUrl: string }) {
return (
<article className="profile-card"> {/* global leakage */}
<img src={avatarUrl} alt="" className="avatar" />
<h2 className="name">{name}</h2>
</article>
);
}
/* app.css (huge shared stylesheet)
.profile-card { border-radius: 6px; background: #fff; padding: 1rem; }
...
/* 2000 lines later another team adds: *
.profile-card { background: var(--brand-accent); } /* overrides earlier rule */
*/
What’s wrong:
ProfileCard
leaves orphaned rules in app.css
.Component.tsx
+ Component.module.css/tsx
.