Concept
CSS has no built-in scoping, every selector is globally visible to every stylesheet on the page, and specificity/cascade order determines the winner when rules conflict. This is fine for a small site; at the scale of a large product with many contributors, it becomes a real liability: a .card class defined by one team can be silently overridden by an unrelated .card elsewhere, and specificity fights escalate (.a .b .c vs #id .x) until someone reaches for !important as a last resort, poisoning the codebase further. BEM and ITCSS are two complementary, long-standing methodologies that address this, BEM through naming discipline, ITCSS through file/specificity organization. (Modern approaches like CSS Modules and Tailwind, covered in their own topics, solve the same underlying problem differently, this topic is about the naming/organization-discipline approach that predates and still coexists with those.)
BEM, Block, Element, Modifier
/* Block: a standalone component */
.card { }
/* Element: a part of the block, connected with __ */
.card__title { }
.card__image { }
/* Modifier: a variant of the block or element, connected with -- */
.card--featured { }
.card__title--large { }<div class="card card--featured">
<img class="card__image" />
<h2 class="card__title card__title--large">Title</h2>
</div>The naming convention itself is the entire point: .card__title unambiguously signals "this is the title element, and it belongs to the card block", purely from reading the class name, with no need to inspect the HTML nesting or guess at relationships. This solves a real problem: without a convention, .title alone gives no indication of which component it belongs to or whether it's safe to modify without checking every other place .title might be used across the codebase.
Why this naming structure specifically matters for specificity: every BEM class is a single class selector (specificity 0-1-0), regardless of how deeply nested the actual HTML is. .card__title--large has the exact same specificity as .card, so BEM-authored CSS naturally avoids the deep-nesting specificity escalation (.card .header .title at specificity 0-3-0) that makes later overrides progressively harder without escalating further or using !important.
ITCSS, Inverted Triangle CSS
ITCSS organizes an entire stylesheet's file/import order by increasing specificity and explicitness, from broadest/most-global to narrowest/most-specific:
1. Settings, variables, config (no actual CSS output, custom properties, Sass vars)
2. Tools, mixins, functions (no CSS output either)
3. Generic, resets, normalize, box-sizing: border-box (very low specificity, broad reach)
4. Elements, bare element selectors: h1, a, p (still low specificity, no classes yet)
5. Objects, layout patterns, class-based, no cosmetic/visual styling: .o-container, .o-grid
6. Components, actual UI components, most of a project's CSS lives here: .c-button, .c-card
7. Utilities, single-purpose overrides, highest specificity/!important allowed here: .u-hidden, .u-text-centerThe core insight: specificity should increase monotonically as you move through the file/import order, matching the natural intent of each layer (a reset should be easily overridable by anything; a utility class should reliably win). Violating this order, a "component" layer rule with higher specificity than a later "utility" layer rule, is exactly what causes the frustrating "I added a utility class but it's not applying" bug, since the utility, despite being intended as the final word, gets beaten by an earlier, accidentally-more-specific component rule.
/* Generic layer, very low specificity, broad reach */
* { box-sizing: border-box; }
/* Elements layer, bare selectors, still low specificity */
a { color: inherit; text-decoration: none; }
/* Components layer, the bulk of real styling */
.c-card { padding: 1rem; border-radius: 8px; }
/* Utilities layer, highest specificity, single-purpose, always wins as intended */
.u-hidden { display: none !important; }Combining BEM naming with ITCSS organization
They're complementary, not competing, BEM answers "how do I name this class so its purpose and relationships are unambiguous," ITCSS answers "where does this rule live in the file/specificity order so the cascade behaves predictably." A mature pre-CSS-Modules/Tailwind codebase commonly uses both together: BEM-named component classes, organized within an ITCSS-structured set of files/import order.
Why these methodologies still matter even in a CSS Modules/Tailwind/CSS-in-JS world
Even when using a scoping solution that eliminates the global namespace collision problem (CSS Modules' automatic class hashing, Tailwind's utility-only approach, CSS-in-JS's component-scoped styles), the naming clarity BEM provides and the specificity discipline ITCSS teaches remain genuinely useful mental models, understanding why deep selector nesting causes specificity problems, and why naming should communicate relationships, informs good practice regardless of which specific tooling layer is scoping the CSS underneath.
Common Mistakes
1. Nesting BEM elements to represent DOM nesting (.card__title__text)
/* Wrong, BEM elements are NOT meant to be chained to represent DOM depth */
.card__title__text { }
/* Right, flat, each element name is relative to the BLOCK, not its immediate DOM parent */
.card__title { }
.card__text { }A common BEM misunderstanding: elements are always relative to the block, not to their immediate DOM parent, there's no "grandchild element" naming pattern. If something is conceptually a distinct sub-component with its own elements, it likely deserves being its own BEM block rather than an ever-deepening element chain.
2. Mixing ID selectors or deep descendant selectors into an otherwise BEM/ITCSS-organized codebase
/* Breaks BOTH BEM's flat-specificity guarantee and ITCSS's monotonic-specificity ordering */
#sidebar .card .title { color: red; }A single high-specificity selector like this can silently defeat the entire discipline the rest of the codebase maintains, it becomes effectively un-overridable by any correctly-BEM-named class later in the cascade, forcing whoever needs to override it into an escalating specificity fight or !important.
3. Putting component-specific styling in the ITCSS "Objects" layer
Objects should be purely structural/layout patterns with no cosmetic styling (.o-grid defines column behavior, not colors or fonts), mixing visual/branded styling into the Objects layer blurs the layer's intent and makes those objects harder to safely reuse across visually different components.
4. Treating BEM's --modifier as a replacement for genuinely separate states that need JS-driven toggling
.card--featured { } /* fine, a static, author-time variant */
.card--is-open { } /* also fine, but ensure JS actually toggles this class consistently */Modifiers work well for both author-time variants and JS-toggled state classes, but a codebase should have a clear, consistent convention (often is-/has- prefixes for JS-toggled state specifically) so it's obvious at a glance which modifiers are static design variants versus dynamic interactive state, mixing the two without a naming distinction makes the CSS harder to reason about.
5. Adding !important outside the Utilities layer
The entire point of ITCSS's monotonically-increasing specificity is that a well-organized codebase should rarely need !important at all, reaching for it in the Components layer is usually a sign that the actual specificity/ordering problem hasn't been addressed, just papered over locally.
Best Practices
- Adopt BEM (or a similar clear naming convention) for any team-authored, class-based CSS codebase not already using CSS Modules/CSS-in-JS/Tailwind's automatic scoping.
- Organize stylesheets with monotonically increasing specificity, roughly following ITCSS's layer structure, even if not adopting its exact 7-layer names verbatim.
- Keep BEM elements flat, relative to the block, not chained to represent DOM depth, promote deeply-nested conceptual sub-components to their own block.
- Reserve
!importantfor the Utilities layer only, treating its appearance elsewhere as a signal the underlying specificity organization needs fixing. - Distinguish static/author-time modifiers from JS-toggled state classes via a clear naming convention (
is-/has-prefixes are a common one). - Recognize these methodologies as complementary to, not competing with, CSS Modules/Tailwind/CSS-in-JS, the underlying specificity/naming-clarity principles remain useful even when a build tool handles the actual scoping.
Further Resources
- BEM, official methodology docs
- CSS-Tricks, BEM 101
- Harry Roberts (csswizardry), ITCSS: Scalable and Maintainable CSS Architecture
- Harry Roberts, CSS Specificity
- Smashing Magazine, BEM for Beginners
