CSS Tutorial 0/203 lessons ~6 min read Lesson 116

    BEM

    bem bem (block, element, modifier) is a naming methodology that maps css selectors to ui component boundaries — not a

    Course progress0%
    Focus
    27 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    BEM (Block, Element, Modifier) is a naming methodology that maps CSS selectors to UI component boundaries — not a preprocessor or framework. Staff engineers adopt BEM when global cascade and specificity wars slow dozens of teams shipping into one stylesheet. The methodology trades longer class strings for predictable selector specificity (0,1,0) and grep-friendly ownership: every class name encodes structure and state without nesting selectors three levels deep.

    At scale, BEM is judged by how fast a new engineer can locate styles for a broken checkout button at 2 a.m., and whether a marketing landing page can ship without raising specificity across the admin dashboard.

    Business problem

    Business pressure: A 40-team product org sharing one global CSS bundle hit recurring production incidents — marketing overrides broke admin tables because both used .active and .header. Refactors took weeks because nobody knew which DOM nodes depended on a renamed class. BEM exists to make style ownership legible at org scale.

    • Velocity: Parallel teams ship components without negotiating global selector namespaces.
    • Regression cost: Specificity escalation from nested SCSS compounds — one !important fix breeds ten more.
    • Onboarding: Class names like card__title--featured communicate DOM contract without opening three partial files.

    Why this feature exists

    Platform history: Yandex open-sourced BEM in 2009 when large Russian portals needed component CSS that survived years of contributor churn. Nested descendant selectors (.nav ul li a) mirror DOM structure — when markup refactors, styles silently detach.

    • Problem solved: Flat, single-class selectors with encoded hierarchy replace fragile nesting.
    • Rejected alternative: ID selectors and deep nesting — high specificity and unmaintainable override chains.
    • Modern role: BEM naming survives inside CSS Modules, Vue SFC scoped blocks, and design-system documentation.

    Browser rendering perspective

    Rendering impact: BEM does not change the cascade algorithm — it changes author habits so selectors stay shallow. Shallow selectors reduce style recalculation scope when DOM subtrees update; deep compound selectors force broader invalidation in Blink's style engine.

    • Chrome (Blink): Class-only selectors match quickly; avoid chaining five classes when one BEM block class suffices.
    • Firefox (Gecko): stylo parallelizes rule matching — fewer unique selector patterns improve cache hit rate.
    • Safari (WebKit): Identical cascade cost; BEM wins are maintainability and bundle grep, not raw paint time.

    Internal browser workflow

    Workflow: Author assigns block class on root → elements use block__element → state uses block--modifier or block__element--modifier. Stylelint enforces pattern; visual regression CI snapshots per block.

    • Match: Single class per node preferred — compound selectors only for rare composition.
    • Cascade: Keep specificity uniform so source order and layer order resolve conflicts predictably.
    • Composition: Blocks nest in DOM but classes stay flat — card inside sidebar does not become sidebar__card unless card is truly owned by sidebar.

    Feature deep dive

    Three layers: Block — standalone component (menu). Element — part of block, no standalone meaning (menu__item). Modifier — variant or state (menu__item--active, button--primary).

    • Block: Reusable UI chunk — search-form, not search-form__input on the wrapper.
    • Element: Double underscore — never block__elem__sub; sub-elements re-block (menu__item + menu-item__icon).
    • Modifier: Double hyphen — boolean (--disabled) or key-value (--size-large).
    • JS hooks: Separate js-toggle from menu--open — behavior classes should not carry presentation.
    css
    .card { }
    .card__header { }
    .card__title { }
    .card__body { }
    .card--featured { border-color: var(--accent); }
    .card__title--truncate { overflow: hidden; text-overflow: ellipsis; }
    /* DOM */
    <article class="card card--featured">
    <header class="card__header">
    <h2 class="card__title card__title--truncate">Title</h2>
    </header>
    <div class="card__body">…</div>
    </article>

    Syntax

    Delimiter rules: Block name is lowercase with hyphens. Element: block__element. Modifier: block--modifier or block__element--modifier. No camelCase in published class strings — consistency beats personal taste.

    css
    /* Valid */
    .btn { }
    .btn__icon { }
    .btn--primary { }
    .btn--size-large { }
    /* Invalid in strict BEM */
    .card__header__title { } /* nested element chain */
    .card.active { } /* use .card--active */

    Examples

    Navigation block — flat classes, no descendant selectors:

    • Hover/focus states: .nav__link:hover — pseudo on element class, not .nav a:hover.
    • Responsive: .nav--stacked modifier on block toggles layout at breakpoint.
    css
    .nav { display: flex; gap: 0.5rem; }
    .nav__link { padding: 0.5rem 1rem; color: var(--text); }
    .nav__link--current { color: var(--accent); font-weight: 600; }
    .nav__link--disabled { opacity: 0.5; pointer-events: none; }

    Real-world use

    Getty Images, BBC GEL, and early Airbnb CSS used BEM-style naming before CSS-in-JS dominance. GitHub Primer documents block-oriented class names. BEM pairs naturally with ITCSS — objects and components layers hold BEM blocks. Teams migrating from Bootstrap often remap utilities to BEM blocks for custom branding without fighting Bootstrap specificity.

    Real production example

    Pattern: E-commerce design system publishes BEM blocks in Storybook — each story documents allowed modifiers. CI runs stylelint-selector-bem-pattern and Percy snapshots per block variant.

    • Ownership: CODEOWNERS per block folder — components/card/.
    • Deprecation: Modifiers marked deprecated in docs for two releases before removal.
    • Bundle: PurgeCSS safelist includes BEM prefixes per deployed route.

    Enterprise use case

    Enterprise: Atlassian Design System and Carbon document component class conventions aligned with BEM thinking — even when using React, the published CSS API uses predictable block-level naming for consumers embedding web components.

    • Polaris: React components map to stable data attributes and class prefixes for theme overrides.
    • Carbon: cds-- prefix acts as namespace — same isolation goal as BEM blocks.
    • Governance: ADR: "no global element selectors in product code — BEM or design-system tokens only."

    Accessibility considerations

    A11y: BEM does not replace semantic HTML. Do not put all styling on div with BEM classes — use button, nav, heading elements. State modifiers must pair with ARIA when state is not visible (aria-expanded on accordion--open).

    • Focus: Style .nav__link:focus-visible — never remove outline without replacement.
    • Color: --disabled modifier must not be the only disabled signal — use disabled attribute.

    Performance considerations

    Performance: BEM's flat selectors are cheap to match. Risk: HTML class bloat increases byte weight on large lists — 10 modifiers × 10k rows adds parse cost marginally; prefer modifier on parent block.

    • Selector length: Avoid 5+ classes per node — composition via single modifier on block.
    • Critical CSS: Extract BEM blocks used above fold per route template.

    SEO considerations

    SEO: Class names are not ranking signals. BEM helps ship consistent responsive layouts that pass mobile-friendly tests — broken CSS hiding content hurts indexing indirectly via CLS and mobile usability.

    • Hidden content: --hidden modifier using display:none removes from render tree — do not hide primary copy.

    Scalability considerations

    Scale: 500+ blocks need naming registry — block names collide (card in checkout vs blog). Use prefixed blocks (checkout-card) or package scopes in monorepos.

    • Micro-frontends: Each remote prefixes blocks (mfe-cart__item) to avoid collision in shared document.
    • White-label: Brand tokens change; BEM structure stays — modifiers swap themes.

    Common production issues

    Production failures: Teams mix BEM with nested SCSS (.card { .title { }) — specificity jumps to 0,2,0 and breaks flat cascade. "Utility creep" adds Tailwind classes beside BEM, doubling HTML weight.

    • Incident: Renamed block without codemod — 40 pages lost styles until hotfix.
    • Drift: __element used on standalone components — grep shows false ownership.

    Debugging guide

    Debug: DevTools → search class substring for block name. stylelint in CI catches invalid patterns. Storybook "class inspector" shows active modifiers.

    • Specificity: Computed tab → compare losing rule specificity when override fails.
    • Missing styles: Often wrong block context — element class without parent block in DOM.

    Best practices

    • One block per component file; elements never nested more than one level in naming.
    • Separate JS behavior hooks from BEM presentation classes.
    • Document allowed modifiers in Storybook prop tables.
    • Use design tokens inside blocks — BEM names structure, tokens name values.

    Anti-patterns

    • .header .nav ul li a — descendant chains defeat BEM purpose.
    • block__elem1__elem2 — flatten to new block.
    • Global tag selectors in component layer — a { color: blue; } breaks all links.

    Trade-offs

    • Benefit: grep-friendly, uniform specificity, clear ownership.
    • Cost: longer class strings and HTML verbosity.
    • vs CSS Modules: BEM is naming discipline; Modules add build-time scoping — often combined.
    • vs utilities: BEM better for complex components; utilities better for one-off layout tweaks.

    Architecture review questions

    • When does an element become its own block?
    • How do we prefix blocks in a micro-frontend shell?
    • What stylelint rules enforce BEM in CI?
    • How do modifiers map to ARIA states for expandable panels?
    • What is our deprecation policy for removed modifiers?

    Interview questions

    Explain Block, Element, Modifier with a real component example.(Beginner)

    A search form block: <code>search-form</code>, elements <code>search-form__input</code> and <code>search-form__button</code>, modifiers <code>search-form--compact</code> or <code>search-form__button--loading</code>. Each class maps to one node; no nesting selectors.

    Follow-up: When is search-form__input its own block?

    Why does BEM forbid .block__elem__subelem?(Intermediate)

    Deep element chains encode DOM depth in the name — when structure changes, names lie. Sub-part becomes its own block (<code>search-input</code>) or single-level element under parent block.

    Follow-up: How does this interact with React component trees?

    BEM vs CSS Modules at enterprise scale?(Advanced)

    BEM is naming; Modules add compile-time scoping. Large orgs use both — Modules prevent accidental global collision; BEM keeps human-readable class strings in DevTools and support tickets.

    Follow-up: What breaks when you only use Modules without naming discipline?

    Hands-on exercise

    Exercise: Refactor a nested SCSS card component to strict BEM. Add stylelint BEM pattern. Storybook stories for each modifier.

    • Before/after specificity count for primary button override.
    • Percy or Chromatic snapshot for --featured and --disabled modifiers.

    Staff engineer notes

    • BEM is an ownership contract — if blocks lack CODEOWNERS, naming discipline decays in one quarter.
    • Interview signal: candidate draws block/element/modifier on whiteboard without mentioning Yandex trivia.
    • Prefix blocks when two teams ship "card" — collision is guaranteed at scale.

    Common pitfalls

    • Mixing BEM with deep SCSS nesting — specificity debt returns.
    • Using modifiers for layout utilities — use a layout layer (ITCSS) instead.

    Try it yourself

    Edit the CSS panel — the preview updates live. Use DevTools Performance and Accessibility panels to validate.

    Try it yourself

    Preview

    Summary

    BEM is a naming methodology for scalable CSS — blocks, elements, and modifiers produce grep-friendly, low-specificity selectors. Staff engineers pair it with lint rules, design tokens, and component ownership to prevent global cascade wars across large product orgs.

    Key takeaways

    • BEM maps CSS classes to component boundaries with flat, uniform specificity.
    • Blocks, elements, modifiers replace fragile descendant selectors.
    • At scale, prefix blocks and enforce patterns with lint and Storybook.
    Ready to mark this lesson complete?Track your journey across the entire course.