Skip to main content
  • Created:
  • Updated:
  • Author:
    Takeshi Takatsudo

BEM Strategy

Historical Context

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.

BEM: Card Component

A nav component where the active state is expressed as a modifier, not a separate class like .active.

BEM: Navigation Component

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.

BEM: Form Component

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);
}
}
BEM with CSS Nesting

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.

References