What are the practical costs of adopting BEM naming across a large CSS codebase, and what would make you conclude it is no longer paying for itself?
answer
- you pay in characters and in renames
- the name lives in four files at once
- nothing checks a convention
- one descendant selector undoes it
- enforce with a lint rule or expect drift
basics
~20 sBEM's costs are verbose markup, long repetitive names, and rename churn across templates, stylesheets, tests and scripts. Its bigger weakness is that nothing enforces it: one descendant selector reintroduces the coupling it removed. It stops paying when discipline cannot be sustained or tooling already guarantees scoping.
solid answer
~50 sThree costs are real. **Verbosity**: class attributes fill with `card__body card__body--compact` strings, and a nine-word block name repeats in every element beneath it. **Rename churn**: a block name lives in the template, the stylesheet, test selectors and sometimes JS hooks, so renaming a component is a cross-file, cross-repo edit — and substring collisions make blind search-and-replace risky. **Zero enforcement**: BEM is a convention, so one `.card .title` descendant selector or one appearance-based block name quietly undoes the flat specificity and the scoping guarantee, and only review catches it. I keep BEM when the team can hold the discipline and enforce the class pattern with a linter, and when JS hooks are kept on separate `js-` prefixed classes so behaviour does not couple to style names. I stop reaching for it when the build already provides real scoping guarantees, since paying the naming tax on top buys much less.
go deeper
Be able to name the obvious cost honestly: the class attributes get long, and renaming a component means editing the same string in the markup and the stylesheet together.
Explain the mechanics of the churn — where the name is duplicated, why substring replacement is unsafe — and that a single descendant selector silently defeats the flat-specificity guarantee.
Show that you have run this in production: how you keep the convention from drifting, why behavioural hooks live on separate classes, and what you look at to decide the tax is no longer worth it.
Own the decision and its enforcement. Argue from consistency economics — a half-followed convention pays all the cost and returns none of the benefit — and make the grammar machine-checked rather than review-dependent.
## The bargain BEM offers BEM buys three things: you can tell what a class belongs to by reading it, every rule weighs the same so conflicts resolve by order, and deleting a component means deleting one prefix. Those are genuine, and on a large multi-team stylesheet they are worth a lot. The interview question is whether you know what you pay for them. ## Cost 1 — verbosity in the markup BEM moves information out of the selector and into the class attribute, so the attribute grows. A component with a five-word block name repeats those five words in every element and again in every modifier: ```html <div class="search-result-list__item search-result-list__item--highlighted"> ``` This is not merely ugly. Long names are harder to scan in diffs, they push templates past comfortable line widths, and they encourage typos that fail silently — a misspelled class matches nothing and produces no error anywhere in the pipeline. ## Cost 2 — rename churn A BEM name is a string duplicated across systems. Renaming a block means touching the stylesheet, every template that instantiates it, integration tests that select by class, snapshot fixtures, and any behavioural code that hooks the same class. None of those references is checked by a compiler. Worse, the names are structured for substring collisions. Renaming `card` by search-and-replace also hits `card__title`, which you want, but also `wildcard`, `credit-card-form` and `card-deck`, which you do not. Teams learn to search for the full delimiter (`card__`, `card--`, `"card"`) and to rename in one atomic commit across repos. That is a real tax on the most common healthy refactor: renaming a thing once you understand it better. ## Cost 3 — the convention is not enforced This is the deepest problem. Nothing in CSS knows what BEM is. The guarantees hold only while every contributor keeps them: - One descendant selector — `.card .card__title` — restores context coupling and unequal specificity for that rule. - One appearance-based block — `.blue-panel` — outlives the colour and misleads every future reader. - One chained element name — `card__body__title` — teaches the next person the wrong grammar by example. - One `class="card js-toggle-card"` collapsed into styling the `js-` class breaks the separation between behaviour and appearance. Each is invisible to the browser and produces no failure. Only review catches them, and review attention decays. The mitigation is mechanical: a stylelint rule such as `selector-class-pattern` can hold class names to a regular expression, and a maximum-specificity rule can reject compound and descendant selectors. If a team is unwilling to run those, the convention will drift, and a drifted convention is worse than none because readers still trust the names. ## Cost 4 — modifier combinatorics Boolean modifiers multiply. Once a button has theme, size, icon-position and loading variants, contributors start writing combination modifiers (`btn--primary-large`) to handle intersections, and the sheet grows quadratically. Key/value modifiers or a strict rule that modifiers never combine to produce a third appearance keep this in check, but it needs deciding up front. ## When it stops paying **When the discipline cannot be sustained.** BEM's value is entirely in consistency. A codebase where half the components follow it delivers none of the scanability and none of the flat specificity, while still paying the verbosity. **When the surrounding tooling already guarantees uniqueness.** The naming convention exists because CSS has one global namespace. Where a build step or platform feature already isolates a component's rules, the manual prefix duplicates a guarantee you already have, and you are paying verbosity for redundancy. **When the components are not shared.** For a small, single-team surface with a handful of components, the collision risk BEM insures against may simply be smaller than the tax. **When names outlive their meaning.** If block names in your codebase describe last year's design language and nobody renames them because the churn is too expensive, the convention has stopped conveying truth and is actively misleading — that is a signal to invest in cheap renaming, not to keep the stale names. ## What I would say in an interview I would keep BEM for a large, long-lived, multi-team stylesheet, and I would pair it with three things: a lint rule on the class pattern so the grammar is machine-checked, separate `js-` prefixed hooks so behavioural code never depends on a style name, and an explicit written decision on the delimiter spelling. Without those, I would expect drift within a year and would rather rely on tooling that enforces scope than on a convention that only asks for it.
- How do you make renaming a BEM block a safe operation?Search for the delimiter-qualified forms — `card__`, `card--`, and the quoted whole word — rather than the bare stem, so `wildcard` and `card-deck` are not swept in. Do it as one atomic commit spanning template, stylesheet and test selectors, and keep behavioural hooks on separate `js-` classes so scripts are unaffected by a purely visual rename.
- What can actually be enforced automatically, and what still needs review?A linter can hold class names to a regular expression, cap specificity, and ban ids and descendant selectors in component files — that covers the grammar and the flat-specificity property. What it cannot judge is whether a block is named for what it is rather than how it looks, and whether a part should have been promoted to a block. Those stay human.
- Why keep JavaScript hooks on separate classes rather than reusing the BEM class?Because a style name and a behaviour contract change for different reasons. If a script queries `.card__toggle`, a designer renaming or restyling the component can break behaviour with no warning. A dedicated `js-toggle` class that carries no declarations makes the coupling explicit: anyone deleting it can see it is a hook, and the visual name stays free to change.
- Is BEM's verbosity avoidable by generating the names?Partly. Preprocessors and template helpers can compose the strings so authors type the block name once, which removes typos and shortens source. It does not remove the churn, since the generated names still appear in tests and in the rendered DOM, and it can make grepping harder because the full name no longer exists literally in the source.
saying these in an interview costs you the question
- Claims BEM prevents collisions rather than merely making them unlikely
- Treats BEM as enforced by the browser or the build
- Says verbosity is purely cosmetic with no maintenance cost
- Renames a block with a bare search-and-replace on the stem
- Couples JavaScript selectors to component style class names