Skip to main content

BEM Naming Explained: How block__element--modifier Tames Your CSS

A practical guide to BEM CSS naming. Learn the block__element--modifier syntax, why flat single-class selectors end specificity wars, and how to name components.

Published By Li Lei
#css #bem #naming convention #front-end #maintainable css

BEM Naming Explained: How block__element--modifier Tames Your CSS

CSS has one property that almost nobody respects until it bites them: specificity. You write .btn { color: blue }, then six months later someone writes .sidebar .btn { color: gray }, and now the sidebar button refuses to turn blue no matter how many times you edit the original rule. The fix is usually another, even more specific selector, then an !important, and within a year the stylesheet is a stack of overrides nobody dares to delete. BEM exists to stop that spiral before it starts.

BEM stands for Block, Element, Modifier. It is a naming convention out of Yandex, and the whole idea fits in one sentence: every styled thing gets exactly one flat class, and the class name itself encodes the structure. No descendant selectors, no nesting in the compiled output, no specificity arms race. Once you internalize the three parts, naming a component stops being a judgment call.

The three parts

A block is a standalone component that means something on its own: card, search, menu, button. You can pick it up and drop it anywhere on the page and it still makes sense.

An element is a part of a block that has no meaning outside it. A card's title is not a free-floating "title"; it is the card's title. BEM joins the element to its block with a double underscore: card__title, menu__item, search__input. The doubled separator is deliberate, because a single hyphen is already spoken for by the names themselves.

A modifier is a flag for appearance or state: featured, active, disabled, large. BEM joins it with a double hyphen: card--featured, menu__item--active. A modifier never travels alone. There is no bare .active class leaking across the page; there is card__title--active, scoped to exactly the thing it modifies.

So the grammar is: a block (card), its elements with a double underscore (card__title), and its variants with a double dash (card--featured). Every one of those is a single class in the markup, which means every selector that targets it has the same low, predictable specificity. There is nothing to override because nothing is more specific than anything else.

Why the doubled separators

The double underscore and double hyphen look fussy until you try to name a multi-word block. Say your block is search-form. That hyphen belongs to the block's own kebab-case name. Now you need its input element. If BEM used a single underscore and single hyphen, search-form-input would be ambiguous: is input an element, or is form-input the name? With doubled separators there is no question: search-form__input is the input element, and search-form__input--focused is that input in its focused state. The boundaries are unmistakable to both a human reader and a regex.

This is also why BEM pairs so well with kebab-case for the names inside each part. Generators like Toolora's BEM Class Name Generator lean on that: type cardTitle, Card Title, or CARD_TITLE and they all normalize to the same card-title segment before the BEM separators get added, so a codebase never drifts into a camelCase-versus-kebab mess. If you are converting a pile of existing names to a single casing first, a dedicated case converter does the bulk pass.

A worked example: the card component

Suppose you sketched a card with a title, a body of text, and a "featured" variant that gets a gold border. Here is the full BEM set:

<article class="card card--featured">
  <h3 class="card__title">Quarterly report</h3>
  <p class="card__body">Revenue is up 12% over last quarter.</p>
</article>

And the stylesheet, flat:

.card { border: 1px solid #ddd; padding: 1rem; }
.card--featured { border-color: gold; }
.card__title { font-size: 1.25rem; margin: 0 0 0.5rem; }
.card__body { color: #444; line-height: 1.5; }

Notice what is missing: no .card .title, no .card.featured .title, no nesting at all. Every rule is a single class. The featured card gets its gold border purely because the element carries both card and card--featured, and card--featured is declared after card, so source order — not specificity — decides the winner. That is the entire trick. When all your selectors weigh the same, the cascade behaves the way you actually expect, and you can reorder, delete, or restyle any piece without playing the override game.

If you author in Sass, the same structure nests cleanly with the & parent selector:

.card {
  border: 1px solid #ddd;
  &--featured { border-color: gold; }
  &__title { font-size: 1.25rem; }
  &__body { color: #444; }
}

The compiler expands &__title back to the flat .card__title, so your source stays readable while the shipped CSS stays specificity-flat. That readable-source, flat-output split is the main reason teams reach for BEM and Sass together.

What I learned the hard way

I came to BEM after inheriting a stylesheet where the homepage hero had a heading that was styled by four different rules, and changing the font size meant editing whichever rule happened to win. I rewrote that one component to BEM as an experiment — hero, hero__heading, hero__heading--compact — and the win was not the names, it was that I could finally delete a rule and know nothing else would break. There was no hidden .section .heading lurking three files away. Since then my rule for naming is boring on purpose: pick the block by asking "could this live anywhere?", make every inner part an element of that block, and never let a modifier mean anything without the thing it modifies attached.

Naming components without overthinking

Two mistakes account for most BEM confusion. The first is nesting elements inside elements: card__body__title. BEM elements are always flat under the block, so even if the title visually sits inside a body wrapper, the name is still card__title. One block, one double underscore, done. The second is letting a modifier go global — a bare .active that some other component then accidentally inherits. Scope it: card__title--active. If you keep those two rules, naming gets mechanical.

When a name is long or has odd characters, normalizing it to clean kebab-case keeps URLs and class names tidy in one move — the same instinct behind a slug generator for paths. And once the stylesheet is written, running it through a CSS minifier before deploy trims the bytes BEM's longer names add, so the readability costs you nothing in production.

BEM is not a framework and it does not need tooling to work. It is a discipline: one flat class per thing, structure encoded in the name, modifiers always scoped. Adopt it on one component, feel the specificity wars stop, and you will not want to write CSS any other way.


Made by Toolora · Updated 2026-06-13