Skip to main content

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

ApproachRuntime CostSSR SupportColocation
CSS ModulesNoneYesYes
TailwindNoneYesInline
styled-componentsYesYes (with setup)Yes
EmotionYesYes (with setup)Yes
Vanilla ExtractNone (build-time)YesYes

Trend: Zero-runtime solutions (CSS Modules, Tailwind, Vanilla Extract) are preferred for performance.