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
| Mistake | Wrong | Correct |
|---|---|---|
| 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__bold | Use a separate utility class |