Skip to main content

What is BEM methodology (block element modifier)

BEM (Block Element Modifier) - a CSS class naming convention where every class follows a block__element--modifier pattern to prevent style collisions across a codebase.

Theory

TL;DR

  • Block = standalone UI piece (button). Element = part that only lives inside that block (button__icon). Modifier = a variation or state (button--primary).
  • Think LEGO: block is the base piece, element attaches only to that piece, modifier swaps its look.
  • Main win: .menu__item--active stays scoped to menus and never clashes with .button--active.
  • Use BEM on teams with 5+ devs or CSS over 10k lines. Skip it for tiny prototypes or CSS-in-JS projects.

Quick example

html
<button class="button button--primary"> <span class="button__icon"></span> Click me </button>
css
.button { padding: 10px; border: 1px solid; } /* Block */ .button__icon { display: inline-block; margin-right: 5px; } /* Element */ .button--primary { background: blue; color: white; } /* Block modifier */ .button__icon--large { font-size: 20px; } /* Element modifier */

The class name tells you the full relationship. button__icon--large is a large icon inside a button. Nothing else, nothing more.

Key difference

A flat class like .active is global. Merge three feature branches and you get three different .active rules fighting each other. That is the most common CSS conflict I have seen in team codebases. BEM namespaces everything: .card--active, .menu__item--active, .button--active are three separate selectors that cannot touch each other.

When to use

  • Teams with 5+ developers or CSS over 10k lines: BEM naming prevents merge conflicts and unintended overwrites.
  • Reusable component libraries: elements tie to their block, so there is no semantic drift over time.
  • Vanilla CSS without build tools: BEM gives you scoping without PostCSS or Webpack.
  • CSS-in-JS (Styled Components, Emotion): skip BEM, the library hashes class names automatically.
  • Tailwind CSS: skip BEM, utility classes replace naming conventions entirely.
  • Small solo project under 5 files: flat classes are faster to write and BEM overhead is not worth it.

Comparison

| Aspect | BEM | Flat CSS (.btn.active) | CSS Modules | |---|---|---| ---| | Naming | block__element--mod | .class, .state | Button_module__root | | Scalability | High (namespaced) | Low (global collisions) | High (hashed at build time) | | Team size | 5+ devs | Solo or small | Any size | | Debugging | DevTools shows hierarchy | Hunt globals | Safe but obfuscated names | | Best for | Vanilla or team CSS | Prototypes | React/Vue components |

How browsers handle BEM

No runtime magic. Browsers parse BEM classes as standard CSS selectors. .button__element has specificity (0,1,0), same weight as any single class. That's it. Structure lives in the name, not in nesting or shadow DOM. Write flat selectors in CSS, never .button .button__icon {}.

Common mistakes

Nesting selectors in CSS:

css
/* Wrong - fails without PostCSS, breaks BEM intent */ .button { .icon { color: red; } } /* Right - always flat */ .button__icon { color: red; }

Chaining too many modifiers on one class:

css
/* Wrong - hard to read, hard to override */ .button--primary--large--rounded { } /* Right - separate modifier classes, combine in HTML */ .button--primary.button--large { }

Deep element nesting:

css
/* Wrong - BEM elements go only one level deep */ .header__nav__link { } /* Right - treat link as an element of nav */ .nav__link { }

Expecting a block modifier to suppress an element modifier:

css
.card { transition: opacity 0.3s; } .card--loading { opacity: 0.5; } /* Affects the block */ .card__spinner--fast { animation: spin 1s infinite; display: block; } /* Reality: --fast spinner still runs when .card--loading is present. * Block and element modifiers are independent selectors. * To hide the spinner during loading, write it explicitly: * .card--loading .card__spinner { display: none; } */

Real-world usage

  • Yandex (where BEM was created) applies it in codebases over 1 million lines: search__input--focused.
  • BBC News uses it for modular templates: article__headline--breaking.
  • Shopify themes follow BEM naming: product-form__input--error.
  • Google AMP uses it consistently: amp-form__submit-button--disabled.

Follow-up questions

Q: Explain block, element, and modifier with an e-commerce example.
A: Block is cart. Element is cart__item (one row in the cart). Modifier is cart__item--out-of-stock (strikethrough price, greyed out). The cart can appear anywhere on the page. The item only makes sense inside the cart.

Q: How does BEM specificity compare to plain classes?
A: .block__element has specificity (0,1,0), same as .element. BEM itself does not boost selector weight. Protection comes from the unique name. No !important needed.

Q: Would you use BEM in a React project that already uses CSS Modules?
A: Probably not. CSS Modules hash class names at build time, so .button in one file never collides with .button in another. Adding BEM on top would add verbosity without solving a problem Modules already handles.

Q: (Senior) If a block has a modifier and one of its elements also has a modifier, does one affect the other?
A: No. .card--loading changes the block. .card__spinner--fast changes the element. They are independent selectors and the browser applies both. If you want the spinner hidden while loading, add an explicit rule: .card--loading .card__spinner { display: none; }.

Examples

Basic button component

html
<button class="button button--primary"> <span class="button__icon"></span> Submit </button> <button class="button button--secondary"> Cancel </button>
css
.button { padding: 10px 20px; border: 1px solid transparent; cursor: pointer; } .button__icon { display: inline-block; margin-right: 6px; } .button--primary { background: #0057ff; color: #fff; } .button--secondary { background: #e0e0e0; color: #333; }

Two buttons, one block, two modifiers. Neither style leaks outside .button.

E-commerce product card

html
<article class="product-card product-card--featured"> <img class="product-card__image" src="shirt.jpg" alt="T-shirt"> <h3 class="product-card__title">Cotton Tee</h3> <div class="product-card__price product-card__price--sale">$19.99</div> <button class="product-card__button product-card__button--add">Add to cart</button> </article>
css
.product-card { display: flex; flex-direction: column; max-width: 300px; } .product-card__image { width: 100%; height: 200px; object-fit: cover; } .product-card__title { font-size: 1.2em; margin: 10px 0; } .product-card__price { font-weight: bold; color: green; } .product-card__price--sale { color: red; text-decoration: line-through; } .product-card__button { padding: 10px; background: #007bff; color: #fff; border: none; } .product-card__button--add:hover { background: #0056b3; }

--featured on the block lets you add a border or badge without touching any element styles. --sale on __price turns the price red with a strikethrough. No selector here needs more specificity than a single class.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?