What is BEM, and what problem does it solve in large codebases?
Advertisement
🧩 Scenario
Architecture Walkthrough
Block, Element, Modifier
BEM is a naming convention with three concepts.
A block is a standalone, meaningful component: card, button, nav, search-form. It makes sense on its own and can be moved anywhere in the document without breaking.
An element is a part of a block that has no meaning outside it: the header of a card, the icon inside a button. Written with two underscores: card__header, button__icon.
A modifier is a variant or state of a block or element: a featured card, a disabled button, a large size. Written with two hyphens: card--featured, button--disabled, card__title--truncated.
The separators are the convention's visible signature: block__element--modifier. The double characters exist so that single hyphens remain available inside multi-word names, which is why search-form__input--invalid parses unambiguously.
The Real Rule: Every Selector Is One Class
The naming is what people notice, but the architectural benefit comes from a constraint that follows from it: every selector is a single class, so every rule has specificity (0, 1, 0).
That flatness is the whole point. With all rules at equal specificity, conflicts are resolved by source order alone, which means any rule can override any other without escalation. Nobody needs to add a parent selector, chain an extra class, or reach for !important to win, because there is nothing to out-rank.
Compare a nested approach. .sidebar .card .title is (0, 3, 0), so overriding it requires at least three classes, and the next override requires four. Average specificity ratchets upward permanently and the codebase becomes progressively harder to change. BEM removes the ratchet by never starting it.
The second benefit of flat selectors is that styling no longer depends on document structure. .card__title matches wherever it appears, so wrapping it in a div, moving it out of the header, or reusing the card in a different container changes nothing. A descendant selector would break on any of those.
Elements Do Not Nest
A frequent misuse is chaining element names to mirror DOM depth: card__header__title. This is incorrect BEM, and understanding why clarifies the whole model.
The element name describes what the part belongs to, which is the block, not where it sits in the tree. A title inside a card is card__title regardless of how many wrappers surround it. Encoding depth in the name reintroduces the structural coupling BEM was designed to remove: rename or remove the header wrapper and every nested class name becomes a lie.
If a part is complex enough to have its own parts, that is the signal it should be its own block. A card containing a media object becomes card plus media, with media__image and media__body, and the two compose.
Modifiers Are Additive
A modifier does not replace the base class; it accompanies it. The markup is class="card card--featured", not class="card--featured" alone.
This keeps the modifier rule small: it only declares what differs from the base. It also keeps specificity flat, since both classes are (0, 1, 0) and the modifier wins by appearing later in the stylesheet. Writing modifiers as standalone classes forces them to duplicate the entire base declaration set.
BEM Alongside Modern Tooling
CSS Modules, styled-components, and framework-scoped styles solve the collision problem automatically by generating unique class names, so the naming discipline is less load-bearing than it was.
What survives is the set of principles underneath. Keep specificity flat inside a component rather than nesting three levels deep because the preprocessor makes it easy. Express variants as explicit modifier classes or props rather than as context-dependent descendant rules. Do not tie styling to document structure. Name parts by what they belong to rather than where they sit.
In practice, teams using CSS Modules often still write BEM-ish names inside a module because it communicates intent to the next reader, and teams using Tailwind get flat specificity by construction while giving up the named component vocabulary. The convention is optional; the principles are not.
Key Code Explained
/* BLOCK: standalone and meaningful on its own */
.card {
background: #fff;
border-radius: 8px;
padding: 16px;
}
/* ELEMENT: a part of the block, two underscores */
.card__header {
display: flex;
justify-content: space-between;
}
.card__title {
font-size: 1.125rem;
font-weight: 600;
}
.card__body {
color: #475569;
}
/* MODIFIER: a variant of a block or element, two hyphens */
.card--featured {
border: 2px solid #3b82f6;
}
.card__title--truncated {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Every selector is ONE CLASS -> every rule is (0, 1, 0).
Overriding never requires escalation, only source order. */
<!-- Modifiers are ADDITIVE: base class plus modifier -->
<article class="card card--featured">
<header class="card__header">
<h3 class="card__title">Title</h3>
</header>
<p class="card__body">Body</p>
</article>
/* WRONG: chaining element names to mirror DOM depth */
.card__header__title {
/* couples the class name to the wrapper structure;
remove the header and the name becomes a lie */
}
/* RIGHT: the element belongs to the BLOCK, not to its position */
.card__title {
/* matches wherever it appears inside the card */
}
/* WRONG: nesting reintroduces specificity escalation */
.card .card__title {
color: red; /* (0, 2, 0) — now overrides need two classes */
}
.sidebar .card .card__title {
color: blue; /* (0, 3, 0) — and the ratchet continues */
}
/* WRONG: a modifier used alone must duplicate the whole base */
.card--featured-standalone {
background: #fff;
border-radius: 8px;
padding: 16px;
border: 2px solid #3b82f6; /* only this line is actually the variant */
}
/* Complex parts become their own BLOCK and compose */
.media { display: flex; gap: 12px; }
.media__image { width: 64px; }
.media__body { flex: 1; min-width: 0; }
/* <div class="card"><div class="media">…</div></div> */
The .card__header__title versus .card__title pair is the most instructive contrast, because the wrong version looks more descriptive and is actually more fragile. Encoding the wrapper in the name means the name has to change whenever the markup does, which is precisely the structural coupling BEM sets out to eliminate.
The nested .card .card__title example shows how easy it is to write BEM names while abandoning the benefit. The class names are correct, the selector is not, and the moment a rule sits at (0, 2, 0) the escalation has started. BEM's value is in the selector shape at least as much as in the naming.
Tradeoffs
| Aspect | BEM | Nested descendant selectors | CSS Modules |
|---|---|---|---|
| Specificity | Flat, always (0, 1, 0) | Escalating | Flat, if written flat |
| Name collisions | Prevented by convention | Possible | Prevented by tooling |
| Coupled to DOM structure | No | Yes | No |
| Class name verbosity | High | Low | Low, generated |
| Requires build tooling | No | No | Yes |
| Communicates intent in markup | Strongly | Weakly | Depends on naming |
| Enforced by anything | Discipline only | N/A | The bundler |
What Interviewers Actually Check
- Whether you can define all three concepts and write the separators correctly
- Whether you identify flat single-class selectors as the architectural benefit, not just the naming
- Whether you know element names do not chain to mirror DOM depth
- Whether you know modifiers accompany the base class rather than replacing it
- Whether you can compare it honestly to CSS Modules and utility-first approaches
Follow-Up Questions
- How do cascade layers change the specificity argument for BEM, given that layers outrank specificity entirely?
- When should a BEM element be promoted to its own block, and what signals that?
- How would you express a state that comes from JavaScript, such as
is-open, within a BEM codebase? - What does BEM offer that CSS Modules does not, and would you use both together?
- How does BEM interact with a design-token system based on custom properties?
Common Candidate Mistakes
- Chaining element names as
card__header__titleto mirror the DOM, which recreates the structural coupling BEM exists to remove - Writing correct BEM names but nesting the selectors, so rules sit at two or three classes and specificity escalation resumes
- Applying a modifier class without its base class, forcing the modifier rule to duplicate every declaration from the base
- Treating BEM as a rule about how markup must be structured, when it is a naming and selector-shape convention that says nothing about DOM depth
- Presenting BEM as obsolete because CSS Modules exist, without recognising that flat specificity, explicit modifiers, and structure independence still apply inside every scoped component
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you define block, element, and modifier, and write the separators from memory?
- Can you explain the specificity benefit of keeping every selector to a single class?
- Can you explain why
card__header__titleis incorrect? - Can you explain why modifiers must accompany the base class?
- Can you say what BEM still adds in a codebase using CSS Modules or a component framework?
Summary
BEM names classes as block__element--modifier. A block is a standalone meaningful component, an element is a part that has no meaning outside its block, and a modifier is a variant or state. The double underscore and double hyphen separators exist so single hyphens remain available inside multi-word names.
The naming is what gets noticed, but the architectural payoff comes from the constraint it enables: every selector is a single class, so every rule has specificity (0, 1, 0). With all rules at equal specificity, conflicts resolve by source order and no override ever needs to escalate by adding a parent, chaining a class, or using !important. It also decouples styling from document structure, since .card__title matches wherever it appears rather than depending on a particular nesting.
Two misuses undo the benefit. Chaining element names as card__header__title encodes DOM depth into the name and reintroduces exactly the coupling BEM removes; the element belongs to the block, not to its position, and a part complex enough to have its own parts should become a block that composes. And writing BEM names while nesting the selectors keeps the vocabulary and discards the flat specificity that made it worth adopting. In a codebase using CSS Modules or a component framework, the naming convention is less load-bearing because scoping is automatic, but the underlying principles of flat specificity, explicit modifiers, and structure independence remain just as applicable inside each component.
Does BEM allow nesting selectors?
No, and that is the point. Every selector is a single class, so specificity stays flat at 0-1-0 and any rule can override any other by source order alone.
Is BEM still relevant with CSS Modules and styled-components?
The naming convention matters less because scoping is automatic, but the underlying principles — flat specificity, explicit modifiers, no reliance on document structure — still apply inside every component.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement