skip to content

In BEM, why is a class such as `card__body__title` considered wrong for an element nested inside another element, and how should deeply nested markup be named instead?

level: middleimportance: should knowfreq 50%

answer

  1. ownership, not a route through the DOM
  2. only two levels exist
  3. depth changes, meaning does not
  4. a part with parts is a block
  5. one node, two classes

basics

~20 s

BEM has only two levels: a block and a flat set of elements it owns. Element names describe ownership, not DOM depth, so the class stays card__title however deep the node sits. If a part needs its own parts, promote it to a block.

solid answer

~50 s

BEM deliberately has no grandchild elements — a block owns a flat namespace of elements, and the name says *who owns this*, not *how to walk to it*. `card__body__title` encodes a DOM path, so any markup change that adds or removes a wrapper invalidates the name and forces an edit in the HTML, the CSS and often the tests. It also grows without bound and buries the block name in the middle of a long string. The rule is that depth in the markup never appears in the class: the heading is `.card__title` whether it is one div down or four. When a part genuinely has its own internal structure and modifiers, that is the signal it should be its own block — extract it as `.media` and mix it into the card as `class="media card__media"`, so the card owns placement and the new block owns its own elements.

code

html · 9 lines
html
<article class="card">
  <div class="card__body">
    <h2 class="card__title">Quarterly report</h2>
  </div>
  <figure class="media card__media">
    <img class="media__image" src="chart.png" alt="Revenue chart">
    <figcaption class="media__caption">Revenue by quarter</figcaption>
  </figure>
</article>

go deeper

for a junior

Remember the shape of a valid BEM name: exactly one block, at most one element, at most one modifier. Two double underscores in one class is always a mistake.

for a middle

Explain why depth does not belong in the name — it is the most volatile fact about a node — and show the mix pattern as the correct alternative to chaining.

for a senior

Demonstrate the promotion judgment: which parts become blocks, why reusable components must not own their own margins, and how that keeps refactors from rippling across templates.

for a principal

Frame it as a cost-of-change decision. Names that encode structure make markup refactors expensive, so argue for a lint rule on the class pattern rather than trusting review to catch chained names.

## The rule BEM's entity model is two levels deep and no more: a **block**, and a flat set of **elements** belonging to it. There is no such thing as an element of an element. `card__body__title` is not a BEM name; the correct name is `card__title`, regardless of how many wrappers sit between the card root and the heading. ```html <article class="card"> <div class="card__body"> <div class="card__header"> <h2 class="card__title">…</h2> <!-- still card__title --> </div> </div> </article> ``` ## Why chaining is a defect, not just a style preference **It encodes a DOM path in a name.** The point of the element prefix is to say which component owns this node. A chained name says something else — which route you take from the root — and that is exactly the information most likely to change. Add a flex wrapper for alignment and every chained descendant name below it becomes a lie. **Rename churn multiplies.** A BEM name lives in at least three places: the template, the stylesheet, and often a test selector or a `js-` hook. Chained names mean an innocuous markup refactor triggers edits in all of them, for nodes whose *meaning* never changed. **It grows without bound.** Nesting is unlimited in HTML, so chained names have no natural ceiling. Once you reach `card__body__header__actions__button` the block name — the one piece a reader actually needs — is drowned in path noise. **It defeats the flat-specificity property.** BEM's other guarantee is that every rule is one class of equal weight. Chained names do not directly change specificity, but teams that chain names almost always start chaining selectors too (`.card__body .card__body__title`), and then depth decides winners again. ## What to write instead **Keep the element flat.** Depth is invisible in the name. Intermediate wrappers that exist only for layout either get their own flat element name (`card__body`) or no class at all if nothing styles them. **Promote to a block when a part earns it.** The real question the chained name is trying to answer is "this thing has parts of its own." In BEM that is the definition of a block. If a card's media area has an image, a caption and a play badge, and can plausibly appear outside a card, extract it: ```css /* the new block owns its own flat elements */ .media { display: grid; } .media__image { } .media__caption { } /* the card owns only where the block sits */ .card__media { margin-block-end: 1rem; } ``` ```html <figure class="media card__media"> <img class="media__image" src="…" alt="…"> <figcaption class="media__caption">…</figcaption> </figure> ``` That two-class node is a **mix**: one node carrying two BEM entities. The `card__media` class contributes only the outer geometry — margin, grid placement — while `media` and its elements are context-free and reusable anywhere. This is the single most useful pattern in BEM, and it is what people reach for chained names instead of. ## How to tell which one you need Ask whether the part means anything on its own. A card footer, a card title, a card badge — meaningless outside a card, so they are elements. An avatar, a price tag, a rating widget — reusable, so they are blocks. When you are unsure, note that leaving something as an element is cheap to promote later, whereas a wrongly promoted block leaks a name into the global namespace that other code may start relying on. ## A second signal: modifiers on a would-be sub-element If you find yourself wanting `card__body__title--large`, you are stacking two levels of naming and a variant on top. That combination is close to unreadable and is a strong hint the inner structure deserves its own block, where the variant becomes a plain `.media__caption--large`. ## What this does *not* mean Flat element names do not mean flat markup. Wrappers are fine, grids nest, components contain components. The rule is only about the class name: the name records ownership, and the browser finds the node by the class alone, so the path never needs to appear in it.

  • If wrappers should not appear in the name, do intermediate wrappers get classes at all?
    Only if something styles them. A wrapper that carries real declarations gets its own flat element name, such as `.card__body`. A wrapper that exists purely to satisfy a layout hack often gets no class — and its presence is worth questioning, since the same result is usually reachable from the parent's grid or flex rules.
  • What signals that an element should be promoted to a block?
    Three signals: it has internal parts of its own, it acquires its own modifiers, or it plausibly appears outside the current block. Any one is enough. Promote it, give it its own flat element namespace, and mix the old element class onto the same node so the parent keeps ownership of placement only.
  • Does mixing two BEM classes on one node cause conflicts between them?
    Not if the split is disciplined: the reusable block sets its own internals, and the parent's element class sets only outer geometry — margin, grid placement, order. Both are single class selectors of equal weight, so if they ever set the same property the later rule in the stylesheet wins, which is why the parent's layout rules are usually loaded after component rules.

saying these in an interview costs you the question

  • Says nesting depth in the markup belongs in the class name
  • Writes card__body__title and calls it strict BEM
  • Chains selectors to make a nested element rule win
  • Adds a wrapper class only to satisfy the chained name
  • Never promotes a complex element into a block of its own

context