Difficulty: Advanced
BEM (Block Element Modifier) is a CSS naming methodology developed by Yandex that provides a structured, predictable way to name CSS classes. It solves the fundamental challenge of CSS at scale: as stylesheets grow, class names collide, specificity wars erupt, and developers cannot safely modify styles without risking breakage elsewhere. BEM addresses these problems through a strict naming convention that makes the relationship between HTML structure and CSS rules immediately visible.
The BEM naming pattern follows the format: block__element--modifier. A Block is a standalone, reusable component that is meaningful on its own (e.g., 'card', 'menu', 'search-form'). An Element is a part of a block that has no standalone meaning and is semantically tied to its block (e.g., 'card__title', 'menu__item', 'search-form__input'). A Modifier is a flag on a block or element that changes its appearance, behavior, or state (e.g., 'card--featured', 'menu__item--active', 'button--disabled'). The double underscore separates blocks from elements, and the double hyphen separates names from modifiers.
BEM's power lies in the constraints it imposes. First, every class is flat -- there are no nested selectors like .card .title, which eliminates specificity escalation. Second, classes are self-documenting: reading 'search-form__input--error' immediately tells you this is the input element of the search-form block in its error state. Third, blocks are independent and portable -- you can move a block from one page to another without its styles breaking because BEM classes do not depend on their DOM context.
In practice, BEM means you never style tag names or IDs, never nest selectors more than one level, and never use descendant combinators for styling. Every styled element gets its own flat BEM class. This feels verbose at first, especially compared to utility-first or nested SCSS approaches, but the tradeoff is predictability. On large codebases with many developers, BEM prevents the most common CSS maintenance problems: unexpected style inheritance, selector specificity conflicts, and the fear of modifying shared styles.
BEM works exceptionally well with CSS preprocessors like SASS/SCSS. The parent selector (&) lets you write nested BEM syntax that compiles to flat selectors. For example, &__title inside .card {} compiles to .card__title. Many teams combine BEM with ITCSS (Inverted Triangle CSS) for file organization, or with component-based architectures where each component has its own BEM-scoped stylesheet. BEM also integrates naturally with component frameworks: a React component named SearchForm would use classes like search-form, search-form__input, and search-form__button.
<!-- BLOCK: Standalone component -->
<form class="search-form">
<!-- ELEMENT: Part of the block (block__element) -->
<input class="search-form__input" type="text" placeholder="Search..." />
<button class="search-form__button" type="submit">Search</button>
</form>
<!-- MODIFIER on block: Variant of the whole component -->
<form class="search-form search-form--dark">
<input class="search-form__input" type="text" />
<button class="search-form__button" type="submit">Search</button>
</form>
<!-- MODIFIER on element: Variant of a single part -->
<form class="search-form">
<input class="search-form__input search-form__input--large" type="text" />
<button class="search-form__button search-form__button--disabled" type="submit">Search</button>
</form>
<style>
/* Block */
.search-form {
display: flex;
gap: 8px;
padding: 16px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
}
/* Elements */
.search-form__input {
flex: 1;
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 14px;
}
.search-form__button {
padding: 8px 16px;
background: #3b82f6;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* Block modifier */
.search-form--dark {
background: #1f2937;
border-color: #374151;
}
/* Element modifiers */
.search-form__input--large {
padding: 12px 16px;
font-size: 18px;
}
.search-form__button--disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
Blocks are standalone components. Elements are parts of blocks, connected by double underscores. Modifiers change appearance or state, connected by double hyphens. Each class is flat (no nesting), self-documenting, and independent of DOM hierarchy.
// SCSS: BEM with parent selector (&)
// Compiles to flat BEM classes
.card {
background: #fff;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
overflow: hidden;
// .card__header
&__header {
padding: 16px;
border-bottom: 1px solid #e5e7eb;
}
// .card__title
&__title {
font-size: 18px;
font-weight: 600;
margin: 0;
}
// .card__body
&__body {
padding: 16px;
}
// .card__footer
&__footer {
padding: 12px 16px;
background: #f9fafb;
display: flex;
justify-content: flex-end;
gap: 8px;
}
// .card__action
&__action {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
// .card__action--primary
&--primary {
background: #3b82f6;
color: #fff;
}
// .card__action--secondary
&--secondary {
background: #e5e7eb;
color: #374151;
}
}
// .card--featured
&--featured {
border: 2px solid #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
}
// .card--compact
&--compact {
.card__body {
padding: 8px 16px;
}
.card__header {
padding: 8px 16px;
}
}
}
SCSS parent selector (&) makes BEM convenient to write while compiling to flat selectors. &__title inside .card compiles to .card__title. &--featured compiles to .card--featured. This keeps the source code organized by component while maintaining BEM's flat specificity.
<nav class="main-nav main-nav--sticky">
<a class="main-nav__logo" href="/">
<img class="main-nav__logo-img" src="logo.svg" alt="Company" />
</a>
<ul class="main-nav__list">
<li class="main-nav__item">
<a class="main-nav__link main-nav__link--active" href="/">Home</a>
</li>
<li class="main-nav__item">
<a class="main-nav__link" href="/products">Products</a>
</li>
<li class="main-nav__item">
<a class="main-nav__link" href="/about">About</a>
</li>
</ul>
<button class="main-nav__toggle" aria-label="Toggle menu">
<span class="main-nav__toggle-bar"></span>
<span class="main-nav__toggle-bar"></span>
<span class="main-nav__toggle-bar"></span>
</button>
</nav>
<style>
.main-nav {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
height: 64px;
background: #fff;
}
.main-nav--sticky {
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.main-nav__logo-img { height: 32px; }
.main-nav__list {
display: flex;
list-style: none;
gap: 8px;
margin: 0;
padding: 0;
}
.main-nav__link {
padding: 8px 16px;
text-decoration: none;
color: #4b5563;
border-radius: 6px;
}
.main-nav__link:hover { background: #f3f4f6; }
.main-nav__link--active {
color: #3b82f6;
font-weight: 600;
}
.main-nav__toggle { display: none; }
</style>
A real navigation component shows BEM's strengths: every piece has a clear, unique class name. main-nav__link--active is immediately readable as 'the active link inside the main-nav block'. The component is entirely self-contained and can be moved anywhere without style conflicts.
/* ANTI-PATTERN 1: Nesting elements inside elements */
/* WRONG: block__element__subelement */
.card__header__title { }
/* CORRECT: Elements belong to the block, not other elements */
.card__title { }
.card__header { }
/* ANTI-PATTERN 2: Using tag selectors with BEM */
/* WRONG: tag-qualified selectors */
div.card { }
a.card__link { }
/* CORRECT: Class selectors only */
.card { }
.card__link { }
/* ANTI-PATTERN 3: Deeply nested descendant selectors */
/* WRONG: Contextual styles defeat BEM's purpose */
.sidebar .card .card__title { color: red; }
/* CORRECT: Use a modifier on the block */
.card--sidebar { }
.card--sidebar .card__title { color: red; }
/* Or better: modifier on the element */
.card__title--highlighted { color: red; }
/* ANTI-PATTERN 4: Modifier without base class */
/* WRONG: Only modifier class applied */
<div class="card--featured">...</div>
/* CORRECT: Base class + modifier class */
<div class="card card--featured">...</div>
/* ANTI-PATTERN 5: Boolean and key-value modifiers mixed up */
/* Boolean modifier: just a flag */
.button--disabled { } /* button IS disabled */
.button--large { } /* button IS large */
/* Key-value modifier: when there are multiple options */
.button--theme-dark { } /* theme = dark */
.button--theme-light { } /* theme = light */
.button--size-small { } /* size = small */
.button--size-large { } /* size = large */
Never chain elements (block__el1__el2). Elements always belong to the block directly. Never qualify with tag names. Always include the base class alongside any modifier class. Use key-value modifiers when there are multiple options for the same property.
BEM, Block, Element, Modifier, CSS Naming Convention, Maintainability