BEM Strategy
BEM is a traditional CSS naming convention from an era before CSS scoping was widely available. It was invented to simulate the "scope" that other programming languages take for granted — preventing class name collisions in plain, global CSS.
Most modern projects don't need BEM. Today's tooling handles scoping automatically:
- CSS Modules — class names are locally scoped at build time
- Vue / Svelte scoped styles —
<style scoped>generates unique selectors per component - Tailwind CSS — utility-first approach removes the need for naming conventions entirely
- CSS-in-JS (styled-components, Emotion) — styles are component-scoped by default
BEM remains valuable as foundational knowledge — it explains why modern scoping solutions were invented and how they think about component boundaries. It's also still relevant when modern tooling isn't available.
Think of BEM as understanding the "why" behind the tools you use today, not as a pattern to reach for in new projects.
The Problem
CSS naming without convention leads to naming collisions, specificity wars, and unclear relationships between styles. In multi-developer projects, different team members invent different naming patterns, creating inconsistency. AI agents often generate arbitrary class names like .title, .container, or .btn-blue — names that inevitably collide across components. Worse, they default to deeply nested selectors like .sidebar .nav ul li a.active that create specificity chains impossible to override without !important.
The Solution
BEM (Block Element Modifier) provides a strict naming convention: .block__element--modifier. This creates flat, single-class selectors that avoid specificity issues entirely and communicate component structure through the names themselves.
- Block: A standalone, reusable component (
.card,.nav,.form) - Element: A part of a block, prefixed with the block name and double underscore (
.card__title,.card__body) - Modifier: A variation of a block or element, suffixed with double hyphen (
.card--featured,.card__title--large)
Every selector has the same specificity (one class), so the cascade becomes predictable and overrides are straightforward.
Code Examples
The BEM Convention
.block → standalone component
.block__element → child part of the block
.block--modifier → variation of the block
.block__element--modifier → variation of an element
Examples:
/* Block */
.card { }
.nav { }
.form { }
/* Element */
.card__title { }
.card__body { }
.card__image { }
/* Modifier */
.card--featured { }
.card__title--large { }
Card Component
A complete card component using BEM naming. The featured modifier changes the card's appearance while maintaining the same structure.
Navigation Component
A nav component where the active state is expressed as a modifier, not a separate class like .active.
Form Component
A form using BEM with an error state modifier on the input. The error styling is scoped to the element through naming, not through parent selectors.
Common Mistakes
Nesting elements too deep
BEM elements should not reflect DOM nesting. Flatten element names to reference only the block.
/* Wrong: mirrors the DOM tree */
.card__body__title__text { }
/* Correct: flat reference to the block */
.card__text { }
Using a modifier without the base class
A modifier class should always be paired with the base block or element class. The modifier only overrides specific properties — the base class provides the foundation.
<!-- Wrong: modifier alone -->
<div class="card--featured">...</div>
<!-- Correct: base + modifier -->
<div class="card card--featured">...</div>
Using BEM for layout
BEM is for naming component classes, not for page-level layout. Layout concerns should use separate utility or layout classes.
<!-- Wrong: BEM for layout -->
<div class="page__sidebar">...</div>
<!-- Better: separate layout from component naming -->
<div class="sidebar">
<nav class="nav">...</nav>
</div>
BEM with Modern CSS
CSS nesting (now supported in all major browsers) makes BEM even more ergonomic. The & selector lets you write all rules inside the block, keeping the naming convention while reducing repetition.
.card {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
&__image {
width: 100%;
display: block;
}
&__title {
font-size: 18px;
font-weight: 600;
}
&__body {
padding: 16px;
}
&--featured {
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
}
}
When to Use
Most modern projects don't need BEM. If you're starting a new project with a frontend framework, you almost certainly have better scoping options available.
What replaced BEM
- CSS Modules → local scope by default, no naming convention needed
- Vue / Svelte scoped styles →
<style scoped>handles collision prevention automatically - Tailwind CSS → no class naming at all, utilities compose directly
- CSS-in-JS → component-level scope baked in
When BEM is still relevant
- No build tools — plain HTML/CSS projects where you're writing global stylesheets
- Legacy codebases — incrementally refactoring toward better architecture
- Multi-team without framework agreement — a shared naming convention prevents collisions when teams can't agree on tooling
- Server-rendered apps — HTML and CSS ship separately without a build step to scope styles
BEM as foundational knowledge
Even if you never write BEM in production, understanding it is worthwhile. It explains why CSS Modules and scoped styles exist, and what problem they're solving. The concepts — flat selectors, single-class specificity, component-scoped naming — carry over into how modern tools think about styles.