BEM Naming Convention
Block Element Modifier (BEM)
BEM is a naming methodology that provides a strict structure for class names, making CSS more predictable and maintainable. Every class follows the pattern block__element--modifier.
/* Block: a standalone component */
.card { border: 1px solid #e0e0e0; border-radius: 8px; }
/* Element: a part of the block */
.card__title { font-size: 1.5rem; font-weight: 700; padding: 16px; }
.card__body { padding: 0 16px 16px; line-height: 1.6; }
.card__image { width: 100%; height: 200px; object-fit: cover; }
/* Modifier: a variation of the block or element */
.card--featured { border-color: #1a73e8; box-shadow: 0 2px 8px rgba(26,115,232,0.2); }
.card__title--large { font-size: 2rem; }
Why BEM Matters
Without a methodology, CSS devolves into a soup of globally-scoped selectors. Two developers independently naming a button component might both choose .button, or worse, one uses .btn while the other uses .button-primary. BEM eliminates this ambiguity by enforcing a consistent naming contract.
BEM in HTML
<article class="card card--featured">
<img class="card__image" src="/hero.jpg" alt="Hero">
<h2 class="card__title card__title--large">Featured Post</h2>
<div class="card__body">
<p class="card__text">This post is highlighted on the homepage.</p>
<a class="card__link card__link--cta" href="/read">Read more</a>
</div>
</article>
Common BEM Pitfalls
- Nesting too deep: Avoid
block__element__element. If you need deeper nesting, the component should be split into smaller blocks. - Ignoring semantic meaning: BEM names should describe purpose, not appearance. Use
card__titleinstead ofcard__big-text. - Overusing modifiers: If a modifier changes more than 30% of the block, consider creating a new block instead.
Utility-First with Tailwind CSS
Utility-First CSS
Utility-first frameworks like Tailwind CSS provide small, single-purpose classes that compose directly in HTML. Instead of writing custom CSS per component, you build designs by combining utilities.
<!-- Tailwind utility classes compose to form a card -->
<div class="max-w-sm rounded-lg border border-gray-200 bg-white shadow-md">
<img class="w-full h-48 object-cover rounded-t-lg" src="/photo.jpg" alt="">
<div class="p-5">
<h3 class="mb-2 text-2xl font-bold text-gray-900">Card Title</h3>
<p class="mb-3 text-base text-gray-600 leading-relaxed">
Description text with utility classes for typography and spacing.
</p>
<a href="/" class="inline-block px-4 py-2 text-sm font-semibold text-white bg-blue-600 rounded hover:bg-blue-700 transition-colors">
Action Button
</a>
</div>
</div>
Tailwind Configuration
Tailwind is configured via tailwind.config.js, where you extend the default design system:
// tailwind.config.js
module.exports = {
content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
brand: {
50: '#f0f4ff',
500: '#3b82f6',
900: '#1e3a5f',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
},
plugins: [],
}
When to Extract Components
Utility-first does not mean writing every class inline forever. As patterns emerge, extract them into reusable components:
// React component extracting Tailwind utilities
function Button({ children, variant = 'primary', ...props }) {
const base = 'inline-block px-4 py-2 text-sm font-semibold rounded transition-colors';
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
};
return (
<button className={`${base} ${variants[variant]}`} {...props}>
{children}
</button>
);
}
CSS Modules and Scoped Styling
CSS Modules
CSS Modules generate unique class names at build time, giving you locally-scoped styles by default. When you import a .module.css file, the class names are transformed into hash-based strings.
/* Button.module.css */
.button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
}
.primary {
background-color: #3b82f6;
color: white;
}
.primary:hover {
background-color: #2563eb;
}
.outline {
background-color: transparent;
border: 2px solid #3b82f6;
color: #3b82f6;
}
.outline:hover {
background-color: #3b82f6;
color: white;
}
// Button.jsx — classes are scoped automatically
import styles from './Button.module.css';
function Button({ variant = 'primary', children }) {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);
}
// Output: <button class="Button_button_a1b2c Button_primary_d3e4f">Click</button>
Global Styles in CSS Modules
Sometimes you need to break out of scoping. CSS Modules supports :global() for this:
/* Global reset that escapes module scope */
:global(body) {
margin: 0;
font-family: sans-serif;
}
/* Compose local class with global utility */
.heading {
composes: title from './typography.module.css';
color: var(--text-primary);
}
CSS Modules vs BEM vs Tailwind
| Approach | Scope | Learning Curve | Best For |
|---|---|---|---|
| BEM | Manual naming convention | Low | Legacy projects, teams familiar with CSS |
| Tailwind | Utility classes in HTML | Medium | Rapid prototyping, design-system-heavy apps |
| CSS Modules | Build-time local scope | Medium | React/Vue apps, component libraries |
Component-Based CSS Architecture
Component-Based CSS
Component-based CSS treats every UI element as an independent, self-contained unit with its own styles, structure, and behavior. This mirrors how modern frameworks like React, Vue, and Svelte organize code.
File Structure
src/
components/
Button/
Button.jsx
Button.module.css
Button.test.js
Card/
Card.jsx
Card.module.css
CardHeader.jsx
CardBody.jsx
Modal/
Modal.jsx
Modal.module.css
useModal.js
styles/
global.css /* Reset, variables, base typography */
utilities.css /* Reusable utility classes */
animations.css /* Shared keyframe animations */
Design Tokens
Use CSS custom properties as a single source of truth for your design system:
/* global.css */
:root {
/* Colors */
--color-primary: #3b82f6;
--color-primary-dark: #2563eb;
--color-surface: #ffffff;
--color-text: #1f2937;
--color-text-muted: #6b7280;
/* Spacing scale */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--space-2xl: 48px;
/* Typography */
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.25rem;
--font-size-xl: 1.5rem;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
--shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
}
Composition Over Inheritance
Favor composing small, reusable style fragments over creating deep inheritance chains:
/* Shared mixins via @apply or composes */
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Compose in components */
.card__header {
composes: flex-center from './utilities.css';
padding: var(--space-md);
border-bottom: 1px solid #e5e7eb;
}
Scaling Architecture
As a codebase grows, introduce a "layers" pattern:
- Base layer — resets, CSS variables, global typography
- Component layer — individual component styles (BEM, CSS Modules, or Tailwind)
- Utility layer — small helper classes for one-off layouts
- Override layer — minimal page-specific adjustments (use sparingly)
This layering ensures specificity stays manageable and new styles never accidentally break existing components.
Quiz
1. In BEM methodology, what does the double dash `--` represent?
2. What is the primary advantage of CSS Modules over BEM naming?
3. Which CSS architecture approach is best for a team rapidly prototyping multiple UI variations without writing custom CSS?
Flashcards
Question
What is BEM and what pattern do its class names follow?
Click to reveal answer
Answer
BEM stands for Block Element Modifier. Class names follow the pattern `block__element--modifier`. A block is a standalone component, an element is a part of the block, and a modifier is a variation that changes appearance or behavior.
Question
How do CSS Modules achieve style isolation?
Click to reveal answer
Answer
CSS Modules transform class names into unique hash-based strings at build time. When you import a `.module.css` file, each class is scoped to the component that imports it, preventing global namespace collisions without requiring manual naming conventions.
Question
What are design tokens in CSS architecture?
Click to reveal answer
Answer
Design tokens are named values stored as CSS custom properties (e.g., `--color-primary`, `--space-md`) that represent the visual primitives of a design system. They act as a single source of truth, ensuring consistency across all components and enabling theme switching.
Revision Notes
Key Takeaways
- 1. BEM provides a strict naming convention (`block__element--modifier`) that prevents class name collisions and makes CSS predictable, but requires team discipline to maintain
- 2. Utility-first frameworks like Tailwind let you build UI rapidly in markup by composing small classes, but should be paired with component extraction once patterns stabilize
- 3. CSS Modules offer build-time scoped class names automatically, eliminating the need for manual naming conventions in component-based frameworks
- 4. A scalable CSS architecture uses layers: base (reset, variables), components (scoped styles), utilities (helpers), and overrides (minimal adjustments)
Interview Tips
- • When asked about CSS architecture, explain the tradeoffs between BEM (manual convention), Tailwind (utility-first), and CSS Modules (build-time scoping) rather than picking one as universally best
- • Be prepared to describe how you would structure a CSS codebase for a large team: design tokens, component-level scoping, import patterns, and a style guide
- • Discuss specific pain points CSS architecture solves: global namespace pollution, specificity wars, style duplication, and difficulty refactoring legacy styles
- • Mention real-world experience with tooling: PostCSS for Tailwind, webpack/vite loaders for CSS Modules, stylelint for enforcing conventions
Cheat Sheet
BEM: block__element--modifier — block = component, element = part, modifier = variation. Tailwind: compose utilities in HTML, extract into components when patterns emerge. CSS Modules: import styles object, use styles.className for scoped styles. Design tokens: CSS custom properties in :root for colors, spacing, typography. Architecture layers: base → components → utilities → overrides.