Skip to main content

BEM (Block Element Modifier): The Complete Guide

BEM is the most widely adopted CSS naming convention in professional web development. Invented at Yandex in 2005 and published publicly in 2010, it provides a strict, predictable naming system that scales from solo projects to teams of 50 developers — and is the standard in WordPress theme and plugin development.

1. What Problem BEM Solves

Without a naming convention, CSS in large projects becomes:

/* ❌ The "div soup" problem — what does this style? What depends on what? */
.header { }
.header-inner { }
.header .title { }
.title { } /* Global? Or header-only? */
.nav { }
.nav li { } /* CSS tied to HTML structure */
.nav li a { } /* Breaks if HTML changes */
.nav li a.active { } /* How specific do I need to be? */
.nav-item { } /* Is this the same as .nav li? */
.is-active { } /* Active header? Active nav? Active button? */

BEM solves this by making every class self-describing — the class name itself tells you what element it styles, what component it belongs to, and its visual variant.


2. The Three Concepts

Block

A standalone, reusable component that is meaningful on its own. A block encapsulates a complete piece of UI.

Examples of blocks: .card .nav .btn .form .modal .hero .sidebar .badge

Element

A child part of a block that has no standalone meaning. It only makes sense inside its block.

Syntax: .block__element (double underscore)
Examples: .card__title .nav__item .form__label .modal__body

Modifier

A visual or behavioral variant of a block or element. It changes appearance, state, or behavior.

Syntax: .block--modifier or .block__element--modifier (double hyphen)
Examples: .btn--primary .card--featured .nav__item--active

3. The BEM Naming Formula

.block {} → The component
.block__element {} → A child of the component
.block--modifier {} → A variant of the component
.block__element--modifier {} → A variant of an element
/* A complete BEM example */
.card {} /* Block */
.card__image {} /* Element: the image inside card */
.card__body {} /* Element: the body content area */
.card__title {} /* Element: title in the body */
.card__excerpt {} /* Element: excerpt text */
.card__footer {} /* Element: card footer */
.card__btn {} /* Element: call-to-action button */

.card--featured {} /* Modifier: featured card variant */
.card--horizontal {} /* Modifier: horizontal layout */
.card--dark {} /* Modifier: dark theme */

.card__title--large {} /* Element modifier: larger title */
.card__btn--outline {} /* Element modifier: outline button variant */

4. HTML + CSS: Full Card Component

<!-- Standard card -->
<article class="card">
<div class="card__media">
<img class="card__image" src="thumbnail.jpg" alt="Post title">
<span class="card__badge">New</span>
</div>
<div class="card__body">
<span class="card__category">WordPress</span>
<h2 class="card__title">Getting Started with BEM</h2>
<p class="card__excerpt">Learn how BEM makes your CSS predictable and maintainable.</p>
</div>
<footer class="card__footer">
<span class="card__date">April 28, 2026</span>
<a class="card__btn" href="#">Read More</a>
</footer>
</article>

<!-- Featured variant — add modifier to the block -->
<article class="card card--featured">
<div class="card__media card__media--tall">
...
</div>
...
</article>

<!-- Horizontal layout variant -->
<article class="card card--horizontal">
...
</article>
/* ── Block ── */
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}

/* ── Elements ── */
.card__media {
position: relative;
overflow: hidden;
}
.card__image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
display: block;
transition: transform 0.5s ease;
}
.card:hover .card__image { transform: scale(1.05); }

.card__badge {
position: absolute;
top: 12px;
right: 12px;
background: var(--color-primary);
color: white;
padding: 2px 10px;
border-radius: var(--radius-full);
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
}

.card__body { padding: var(--space-6); flex: 1; }

.card__category {
display: inline-block;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--color-primary);
margin-bottom: var(--space-2);
}

.card__title {
font-size: var(--text-xl);
font-family: var(--font-heading);
line-height: 1.3;
color: var(--color-text);
margin-bottom: var(--space-2);
}

.card__excerpt {
color: var(--color-text-muted);
font-size: var(--text-sm);
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}

.card__footer {
padding: var(--space-4) var(--space-6);
border-top: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
}

.card__date { font-size: var(--text-sm); color: var(--color-text-muted); }

.card__btn {
display: inline-block;
padding: 0.5rem 1.2rem;
background: var(--color-primary);
color: white;
border-radius: var(--radius-full);
font-size: var(--text-sm);
font-weight: 600;
transition: background 0.2s;
}
.card__btn:hover { background: var(--color-primary-dark); }

/* ── Block Modifiers ── */
.card--featured {
border-color: var(--color-primary);
box-shadow: 0 0 0 1px var(--color-primary), var(--shadow-md);
}
.card--featured .card__title { font-size: var(--text-xl); }

.card--horizontal {
display: flex;
flex-direction: row;
}
.card--horizontal .card__media { width: 200px; flex-shrink: 0; }
.card--horizontal .card__image { width: 100%; height: 100%; aspect-ratio: auto; }

/* ── Element Modifiers ── */
.card__btn--outline {
background: transparent;
border: 2px solid var(--color-primary);
color: var(--color-primary);
}
.card__btn--outline:hover {
background: var(--color-primary);
color: white;
}

.card__media--tall .card__image { aspect-ratio: 4 / 3; }

5. BEM Navigation Component

<nav class="nav">
<div class="nav__container">
<a class="nav__logo" href="/">BrandName</a>

<ul class="nav__list">
<li class="nav__item">
<a class="nav__link" href="/about">About</a>
</li>
<li class="nav__item nav__item--has-dropdown">
<a class="nav__link nav__link--active" href="/services">Services</a>
<ul class="nav__dropdown">
<li><a class="nav__dropdown-link" href="/services/web-design">Web Design</a></li>
<li><a class="nav__dropdown-link" href="/services/seo">SEO</a></li>
</ul>
</li>
<li class="nav__item">
<a class="nav__link nav__link--cta" href="/contact">Get Started</a>
</li>
</ul>

<button class="nav__hamburger nav__hamburger--open" aria-label="Open menu">
<span class="nav__hamburger-line"></span>
<span class="nav__hamburger-line"></span>
<span class="nav__hamburger-line"></span>
</button>
</div>
</nav>
.nav {
position: sticky;
top: 0;
z-index: 200;
background: rgba(15, 15, 15, 0.85);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--color-border);
}

.nav__container {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--space-6);
height: 70px;
}

.nav__logo {
font-family: var(--font-heading);
font-size: 1.5rem;
font-weight: 700;
color: var(--color-text);
text-decoration: none;
}

.nav__list {
display: flex;
align-items: center;
gap: var(--space-2);
list-style: none;
}

.nav__item { position: relative; }

.nav__link {
display: block;
padding: 0.5rem 0.875rem;
color: var(--color-text-muted);
font-size: 0.9rem;
font-weight: 500;
text-decoration: none;
border-radius: var(--radius-sm);
transition: color 0.2s, background 0.2s;
}
.nav__link:hover { color: var(--color-text); background: rgba(255,255,255,0.06); }

/* Element modifier: active state */
.nav__link--active {
color: var(--color-primary);
font-weight: 600;
}

/* Element modifier: CTA button */
.nav__link--cta {
background: var(--color-primary);
color: white;
padding: 0.5rem 1.25rem;
border-radius: var(--radius-full);
}
.nav__link--cta:hover { background: var(--color-primary-dark); color: white; }

/* Dropdown */
.nav__dropdown {
position: absolute;
top: calc(100% + 8px);
left: 0;
list-style: none;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: 0.25rem;
min-width: 200px;
box-shadow: var(--shadow-md);
opacity: 0;
transform: translateY(-8px);
pointer-events: none;
transition: opacity 0.2s, transform 0.2s;
}
.nav__item--has-dropdown:hover .nav__dropdown {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.nav__dropdown-link {
display: block;
padding: 0.5rem 0.875rem;
color: var(--color-text-muted);
font-size: 0.875rem;
border-radius: calc(var(--radius-md) - 2px);
transition: background 0.15s, color 0.15s;
}
.nav__dropdown-link:hover { background: rgba(108,99,255,0.1); color: var(--color-primary); }

/* Hamburger */
.nav__hamburger {
display: none;
flex-direction: column;
gap: 5px;
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
}
.nav__hamburger-line {
display: block;
width: 24px;
height: 2px;
background: var(--color-text);
transition: transform 0.3s, opacity 0.3s;
}
/* Modifier: open state */
.nav__hamburger--open .nav__hamburger-line:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); }
.nav__hamburger--open .nav__hamburger-line:nth-child(2) { opacity: 0; }
.nav__hamburger--open .nav__hamburger-line:nth-child(3) { transform: rotate(-45deg) translate(5px, -5px); }

@media (max-width: 768px) {
.nav__list { display: none; }
.nav__hamburger { display: flex; }
}

6. BEM Button System

<!-- Block: .btn -->
<button class="btn">Default</button>
<button class="btn btn--primary">Primary</button>
<button class="btn btn--outline">Outline</button>
<button class="btn btn--ghost">Ghost</button>
<button class="btn btn--danger">Danger</button>

<!-- Size modifiers -->
<button class="btn btn--primary btn--sm">Small</button>
<button class="btn btn--primary btn--lg">Large</button>

<!-- State modifiers -->
<button class="btn btn--primary btn--loading">Loading</button>
<button class="btn btn--primary" disabled>Disabled</button>

<!-- With icon element -->
<button class="btn btn--primary">
<span class="btn__icon"></span>
<span class="btn__label">Download</span>
</button>
/* ── Base block ── */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.7rem 1.5rem;
border: 2px solid transparent;
border-radius: var(--radius-full);
font-family: var(--font-body);
font-size: var(--text-base);
font-weight: 600;
line-height: 1;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
transition: all 0.2s ease;
background: transparent;
color: var(--color-text);
}
.btn:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 3px;
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }

/* ── Block modifiers: variants ── */
.btn--primary { background: var(--color-primary); color: white; }
.btn--primary:hover { background: var(--color-primary-dark); }

.btn--outline {
border-color: var(--color-primary);
color: var(--color-primary);
}
.btn--outline:hover { background: var(--color-primary); color: white; }

.btn--ghost { color: var(--color-text-muted); }
.btn--ghost:hover { background: rgba(255,255,255,0.06); color: var(--color-text); }

.btn--danger { background: var(--color-danger); color: white; }
.btn--danger:hover { background: #c0392b; }

/* ── Block modifiers: sizes ── */
.btn--sm { padding: 0.4rem 1rem; font-size: var(--text-sm); }
.btn--lg { padding: 1rem 2rem; font-size: 1.125rem; }
.btn--xl { padding: 1.25rem 2.5rem; font-size: 1.25rem; }
.btn--block { width: 100%; }

/* ── Block modifier: loading state ── */
.btn--loading { position: relative; color: transparent; pointer-events: none; }
.btn--loading::after {
content: '';
position: absolute;
width: 1rem; height: 1rem;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%;
animation: btn-spin 0.7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* ── Elements ── */
.btn__icon { font-size: 1em; flex-shrink: 0; }
.btn__label { }

7. BEM Rules and Conventions

✅ BEM Do's

/* ✅ Use double underscore for elements */
.card__title {}

/* ✅ Use double hyphen for modifiers */
.card--featured {}

/* ✅ Mix block classes for modifiers — never override without the modifier class */
/* HTML: <div class="card card--featured"> */
.card { padding: 1rem; }
.card--featured { border-color: gold; }

/* ✅ Elements belong to ONE block only */
.card__title {} /* Belongs to .card only */
.modal__title {} /* Different element for modal */

/* ✅ CSS selects by block + modifier only — no descendant selectors */
.card--featured .card__title { font-size: 2rem; }

❌ BEM Don'ts

/* ❌ No grandchild elements — max one __ per class */
.card__body__title {} /* WRONG: three levels */

/* ❌ No tag selectors in BEM */
.card h2 { } /* WRONG: tied to HTML structure */
.card > p { } /* WRONG: breaks if you change tag */

/* ❌ No ID selectors */
#card { } /* WRONG: not reusable */

/* ❌ Don't nest more than 2 levels in Sass */
.card {
&__title {
&__span { } /* WRONG: grandchild */
}
}

8. BEM with CSS Nesting (Modern)

/* Using native CSS nesting with BEM */
.card {
background: var(--color-surface);
border-radius: var(--radius-lg);

/* Elements */
.card__image { width: 100%; display: block; }
.card__body { padding: var(--space-6); }
.card__title { font-size: var(--text-xl); }
.card__excerpt { color: var(--color-text-muted); }
.card__footer { border-top: 1px solid var(--color-border); }

/* Block modifier alters elements */
&.card--featured {
border-color: var(--color-primary);

.card__title { color: var(--color-primary); }
.card__badge { display: block; }
}

/* Hover state */
&:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
}

9. BEM File Structure (Sass/CSS)

scss/
├── components/
│ ├── _card.scss ← One file per block
│ ├── _btn.scss
│ ├── _nav.scss
│ ├── _hero.scss
│ ├── _modal.scss
│ ├── _form.scss
│ └── _badge.scss
├── layout/
│ ├── _container.scss
│ ├── _grid.scss
│ └── _sidebar.scss
├── base/
│ ├── _reset.scss
│ ├── _variables.scss
│ └── _typography.scss
└── main.scss

Each component file contains all elements and modifiers for that block:

// _card.scss
// ==========
// Card Component
// Block: .card
// Elements: .card__image, .card__body, .card__title, .card__footer
// Modifiers: .card--featured, .card--horizontal, .card--dark
// ==========

.card { ... }
.card__image { ... }
.card__body { ... }
.card__title { ... }
.card__footer { ... }

.card--featured { ... }
.card--horizontal { ... }

10. BEM State: JS-Driven (is- Classes)

BEM modifiers handle visual variants (set in HTML). For JavaScript-driven states, the community convention is to use is- or has- prefixes as separate non-BEM classes:

<div class="card is-loading">...</div>
<div class="nav is-open">...</div>
<form class="form has-error">...</form>
<div class="modal is-visible">...</div>
/* State classes are NOT BEM — they're helpers toggled by JS */
.is-loading { opacity: 0.5; pointer-events: none; }
.is-hidden { display: none; }
.is-visible { display: block; }
.is-active { color: var(--color-primary); }
.has-error { border-color: var(--color-danger); }
.has-success { border-color: var(--color-success); }

/* Combine with BEM blocks for specificity */
.card.is-loading { /* card-specific loading */ }
.nav.is-open .nav__list { display: flex; flex-direction: column; }

11. BEM in WordPress (GeneratePress + Bricks)

WordPress theme markup often follows BEM-like patterns. When writing child theme CSS:

/* GeneratePress uses component-like classes */
.site-header → treat as block
.site-header a → avoid! Use .site-header__nav-link
.main-navigation → block
.main-navigation .main-navigation-menu → override carefully

/* When adding your own via Additional CSS */
.gp-card { } /* Your BEM block */
.gp-card__title { } /* Your BEM element */
.gp-card--featured { } /* Your BEM modifier */

/* Bricks Builder generates structured classes */
.brxe-container { } /* Bricks block */
/* Add your classes via Bricks custom CSS field */
.my-hero { }
.my-hero__title { }
.my-hero--dark { }

12. BEM + CSS Variables Integration

Combine BEM's organizational clarity with CSS custom properties for theming:

/* Component-scoped variables with BEM */
.card {
/* Scoped token defaults */
--card-bg: var(--color-surface);
--card-radius: var(--radius-lg);
--card-padding: var(--space-6);
--card-shadow: var(--shadow-md);

background: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: var(--card-shadow);
}

/* Modifier overrides only the tokens it needs */
.card--dark {
--card-bg: #0a0a0a;
}
.card--glass {
--card-bg: rgba(255,255,255,0.05);
backdrop-filter: blur(20px);
}

/* Theme override at page level */
.theme-blue .card { --card-shadow: 0 4px 20px rgba(0,120,255,0.3); }

13. BEM Quick Reference

Block: .block
Element: .block__element (double underscore)
Modifier (block): .block--modifier (double hyphen)
Modifier (elem): .block__elem--mod (double hyphen)
State (JS): .is-state (separate convention)
Grandchild: ❌ Never

Max depth: block > element (not block > element > element)
Selector: One class only (avoid .parent .child)
Nesting in CSS: .block { .block__elem {} &.block--mod {} }

14. Common BEM Mistakes

MistakeWrongCorrect
Grandchild.card__body__title.card__title
Tag selector.card h2.card__title
Single hyphen.card-title.card__title
Overly generic.title.card__title
State as modifier.card--active (JS state)is-active or aria-selected
Global utility in BEM.card__boldUse a separate utility class