Architecture & Patterns
BEM (Block Element Modifier)
Naming convention for scalable, readable CSS:
/* Block */
.card { }
/* Element (part of block) */
.card__title { }
.card__image { }
/* Modifier (variant) */
.card--featured { }
.card__title--large { }
Pros: Predictable, low specificity, self-documenting. Cons: Verbose class names, doesn't prevent unused CSS.
CSS Modules
Locally scoped class names — no collisions:
/* Button.module.css */
.primary { background: #3b82f6; color: white; }
.large { padding: 12px 24px; }
import styles from './Button.module.css';
<button className={`${styles.primary} ${styles.large}`}>Click</button>
// Compiles to: <button class="Button_primary_x7d Button_large_k3f">
Utility-First (Tailwind)
Compose styles with single-purpose classes:
<button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors">
Click
</button>
Pros: No naming, small bundle (purges unused), fast iteration. Cons: Verbose markup, learning curve, harder to extract patterns.
CSS Custom Properties (Design Tokens)
:root {
--color-primary: #3b82f6;
--color-primary-hover: #2563eb;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--radius-md: 0.5rem;
--font-size-base: 1rem;
}
/* Theme switching */
[data-theme="dark"] {
--color-primary: #60a5fa;
--bg: #0f172a;
--text: #f1f5f9;
}
.btn {
background: var(--color-primary);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-md);
}
Dynamic Custom Properties
.progress-bar {
--progress: 0;
width: calc(var(--progress) * 1%);
transition: width 300ms;
}
element.style.setProperty('--progress', 75);
CSS-in-JS Trade-offs
| Approach | Runtime Cost | SSR Support | Colocation |
|---|---|---|---|
| CSS Modules | None | Yes | Yes |
| Tailwind | None | Yes | Inline |
| styled-components | Yes | Yes (with setup) | Yes |
| Emotion | Yes | Yes (with setup) | Yes |
| Vanilla Extract | None (build-time) | Yes | Yes |
Trend: Zero-runtime solutions (CSS Modules, Tailwind, Vanilla Extract) are preferred for performance.