Articles

BEM vs Utility-First CSS: Which Naming Wins?

BEM and utility-first CSS (Tailwind's model) solve the same styling problem in opposite ways. How each works, and when to reach for one over the other.

Takina Takina · · 4 min read
Close-up of CSS code on a screen

BEM (Block, Element, Modifier) and utility-first CSS are two competing answers to the same question: how do you name and scope styles so a large codebase doesn’t collapse into specificity wars and dead selectors. BEM writes semantic, component-scoped class names by convention. Utility-first frameworks like Tailwind skip naming entirely and compose styles from small, single-purpose classes applied directly in markup. Neither is objectively better — they trade off differently on readability, markup size, and how much CSS you actually write.

How BEM works

BEM is a naming convention, not a tool — it needs no build step or library. A component (the “block”) gets a class name, its internal parts (“elements”) are named with a double underscore, and state or variant flags (“modifiers”) use a double hyphen:

<div class="card card--featured">
  <h2 class="card__title">Headline</h2>
  <p class="card__body">Summary text</p>
  <button class="card__action card__action--disabled">Read more</button>
</div>
.card { border-radius: 8px; }
.card--featured { border: 2px solid gold; }
.card__title { font-size: 1.25rem; }
.card__action--disabled { opacity: 0.5; pointer-events: none; }

Every selector is a single flat class, so specificity stays predictable — no descendant combinators, no cascade layers needed to keep one component’s styles from leaking into another’s. The cost is verbosity: every visual variant needs a new class, and the CSS file grows alongside the component library.

How utility-first CSS works

Utility-first frameworks invert the relationship. Instead of naming a component and writing its styles once, you compose a look directly in markup from small, purpose-built classes, each mapping to one CSS declaration or a constrained set of them:

<div class="rounded-lg border-2 border-gold-400 p-4">
  <h2 class="text-xl font-semibold">Headline</h2>
  <p class="text-gray-600">Summary text</p>
  <button class="opacity-50 pointer-events-none">Read more</button>
</div>

There’s no card.css to maintain because there’s no .card class — the styling lives entirely in the class attribute. A design token system underpins the utility scale (spacing, color, and type values are constrained to a predefined set), which is what keeps utility-first markup from becoming an arbitrary pile of magic numbers.

Comparing the two

BEMUtility-first
Where styles liveSeparate CSS file, one rule per classInline in markup, composed from many classes
Naming effortHigh — every block/element/modifier needs a nameNone — classes are predefined
Markup verbosityLow — one or two classes per elementHigh — often 5-15 classes per element
CSS file growthGrows with every new component/variantStays roughly flat after initial setup
Refactoring a lookEdit one CSS rule, affects every instanceEdit markup everywhere it’s duplicated (or extract a component)
Specificity controlExplicit by convention (flat classes)Enforced by the framework’s generated output
Learning curveRead the convention, apply consistentlyMemorize (or look up) the utility vocabulary

When each approach fits

BEM tends to hold up better in codebases with few, highly reused components and a small number of contributors who need to reason about a stylesheet in isolation — design systems and component libraries, in particular, benefit from a stable, named vocabulary that a style guide can document. It also plays well with vanilla CSS and doesn’t require any tooling, which matters for projects that avoid a build step.

Utility-first fits component-oriented frameworks especially well — React, Vue, or Svelte components already encapsulate markup and logic together, so pushing styling into the same file removes a layer of indirection (no jumping between a .jsx file and a .css file to see what a class does). It also sidesteps naming entirely, which is a real cost BEM imposes: naming things is hard, and BEM asks you to do it constantly. The tradeoff is markup that’s harder to skim and a dependency on tooling (a build step and, usually, an editor plugin for autocomplete) to make the utility classes tolerable to write by hand.

Nothing stops a codebase from mixing both: utility classes for one-off layout and spacing, with a handful of BEM-named component classes for genuinely reusable, complex UI (a modal, a data table). The goal in either case is the same one CSS cascade layers and CSS nesting are also trying to solve — keeping specificity predictable as a project grows — just approached from opposite ends.

The takeaway

BEM and utility-first CSS both exist to stop specificity conflicts and dead code from piling up as a project scales, but they get there differently: BEM keeps styles centralized and named, utility-first keeps them inline and pre-defined. Pick BEM when you want a stable, documented vocabulary and are fine writing more CSS; pick utility-first when you want to move fast inside component files and are fine with longer class attributes. Most teams land somewhere in between rather than adopting either dogmatically.

Takina Takina · · 4 min read

CSS field-sizing Property Explained

The CSS field-sizing property lets form controls like textareas grow to fit their content automatically, without JavaScript resize listeners.

#CSS #Web Development #Frontend