← Blog
14 Jul 2026bem naming conventioncss architecturevue componentsweb componentstailwind css

Master BEM Naming Convention: Clean, Scalable CSS

Master the BEM naming convention for cleaner, more scalable CSS. Learn syntax, anti-patterns, and modern use with Vue, Web Components, and Tailwind CSS.

Master BEM Naming Convention: Clean, Scalable CSS

You open a component, change one margin or font rule, and three unrelated screens shift. A modal button suddenly inherits styles from a checkout form. A Vue wrapper renders fine in isolation, then breaks when dropped into a product page with older CSS. That’s usually the moment teams stop asking whether naming matters and start asking how to make CSS predictable again.

The BEM naming convention still solves that problem well. Not because it’s trendy, but because it gives a team a shared way to describe UI pieces without leaning on fragile selector chains. In modern apps, that matters even more. You’re not just styling static pages anymore. You’re styling Vue components, headless primitives, slotted content, generated markup, and states that appear only after user interaction or JavaScript composition.

Table of Contents

Why the BEM Naming Convention Still Matters in 2026

Large CSS codebases don’t usually fail all at once. They decay through small compromises. Someone adds a quick descendant selector. Someone else overrides it with a more specific rule. A third developer avoids touching the file because every change feels risky.

That’s the environment where BEM earns its keep. It creates a naming system that tells developers what a class is, what it belongs to, and how far its responsibility should reach. Instead of guessing whether .title is global, local, or accidental, you see .product-card__title and know exactly what you’re touching.

That clarity is one reason BEM has lasted. BEM remains one of the most widely adopted CSS naming conventions globally in 2026, with organizations including Yandex, Google, BBC, Alfa Bank, and BuzzFeed implementing it in production-grade web applications according to Valorem Reply’s guide to the BEM methodology. Teams don’t keep conventions around for that long unless they keep paying off in maintenance.

Shared vocabulary beats clever CSS

A good naming convention does more than organize files. It creates a common language across design, front-end, and code review.

  • Blocks identify components: A class like .search-form or .user-menu tells the team where ownership starts.
  • Elements show internal structure: .search-form__input is obviously part of the block, not a reusable global pattern.
  • Modifiers expose intent: .user-menu--compact or .button--danger communicates variation without inventing a new component.

Practical rule: If a teammate can’t tell what a class belongs to from the name alone, the name is probably too weak.

BEM also lines up with broader engineering discipline. If your team is already thinking about readable systems, predictable abstractions, and naming that reduces future cleanup, the same logic behind mastering clean code principles applies directly to CSS.

Why it still fits current front-end work

Modern tooling didn’t eliminate CSS architecture. It shifted where the problems show up. Vue single-file components, custom elements, and utility frameworks reduce some global leakage, but they don’t replace naming decisions. Teams still need stable class contracts for markup, tests, theming, debugging, and long-term refactors.

BEM works because it stays simple under pressure. It doesn’t depend on one framework, one build system, or one styling trend. It gives teams a durable baseline when the stack keeps changing.

Deconstructing BEM The Core Concepts

BEM stands for Block, Element, Modifier. The idea came from a real scaling problem, not from theory. It was originally developed in 2005 by a team at Yandex to solve unambiguous CSS class naming across large-scale projects, and it was formally named “BEM” in 2009, as documented by the Web Design Museum’s history of BEM.

That origin matters. BEM wasn’t designed for toy examples. It was built for interfaces large enough that vague naming stopped working.

A diagram explaining BEM naming convention core concepts using LEGO blocks as analogies for blocks, elements, and modifiers.

Blocks are the standalone pieces

Think of a block as the main Lego brick. It’s a complete unit with a clear job. Examples include:

  • .button
  • .card
  • .site-header
  • .checkout-summary

A block should make sense on its own. If you move it to another page or another part of the layout, the name should still hold up.

Bad block names usually describe appearance instead of responsibility. .blue-box and .large-text aren’t blocks. They’re styling shortcuts. A block should describe a component or domain object, not a paint decision.

Elements belong to the block

An element is a part of the block. It can’t stand alone without losing meaning. In BEM syntax, elements use a double underscore.

Examples:

  • .card__title
  • .card__image
  • .card__actions

If the block is the Lego base, the element is a piece attached to it. You don’t treat it as an independent object because it only makes sense as part of that one component.

A useful test is simple. If you saw the class name outside the component, would it still be meaningful? If not, it’s probably an element.

BEM quickly becomes practical. Developers stop writing selectors like .card h2 or .card img. Instead, they name the exact role each part plays. That makes refactoring less risky because the CSS isn’t tied to a specific tag.

Modifiers describe variation

A modifier changes the appearance or state of a block or element. It answers questions like: is this version featured, disabled, compact, selected, or loading?

You’ll commonly see patterns like these:

BEM part Example Meaning
Block .alert The base component
Element .alert__icon A part inside the alert
Modifier on block .alert--error A variant of the alert
Modifier on element .alert__icon--hidden A variant of the icon

In many teams, the exact separator for modifiers varies in day-to-day use. What matters most is consistency within the codebase and a clear distinction between the base component, its parts, and its variants.

The historical BEM syntax documented in the Yandex lineage uses block names in lowercase Latin letters with hyphen-separated words, elements separated by __, and modifiers separated by _ with values when needed. In practice, many front-end teams also use a double-hyphen style for modifiers because it’s visually easy to scan. The key is to choose one house style and enforce it.

BEM Syntax A Practical Guide with Examples

Rules get useful when they survive contact with real markup. So take a common component: a card with an image, title, body text, and action button.

A MacBook Pro showing code editor screen demonstrating BEM naming convention structure with colorful watercolor artistic background.

A simple card example

<article class="card">
  <img class="card__image" src="shoe.jpg" alt="Running shoe">
  <h2 class="card__title">Trail Runner</h2>
  <p class="card__content">Built for daily miles and uneven ground.</p>
  <div class="card__actions">
    <button class="card__button">Add to cart</button>
  </div>
</article>
.card {
  border: 1px solid #d9d9d9;
  border-radius: 12px;
  background: #fff;
  padding: 1rem;
}

.card__image {
  display: block;
  width: 100%;
  border-radius: 8px;
}

.card__title {
  margin-top: 0.75rem;
  font-size: 1.125rem;
}

.card__content {
  margin-top: 0.5rem;
  color: #444;
}

.card__actions {
  margin-top: 1rem;
}

.card__button {
  padding: 0.75rem 1rem;
}

Nothing here depends on tag nesting in the selector. That’s the win. You can change the <h2> to a <div>, move the button into a footer wrapper, or render the content through a Vue slot without rewriting selector chains.

How modifiers look in real markup

Now add variations. Maybe one card is featured, and one button is disabled.

<article class="card card--featured">
  <img class="card__image" src="shoe.jpg" alt="Running shoe">
  <h2 class="card__title">Trail Runner</h2>
  <p class="card__content">Built for daily miles and uneven ground.</p>
  <div class="card__actions">
    <button class="card__button card__button--disabled" disabled>
      Sold out
    </button>
  </div>
</article>
.card--featured {
  border-color: #111;
  box-shadow: 0 0 0 2px #111;
}

.card__button--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

This is the practical pattern commonly adopted every day:

  • Base class first: .card
  • Element for internal parts: .card__button
  • Modifier layered on top: .card--featured

Keep the base class in the markup. A modifier should never replace it. The modifier changes the thing. It doesn’t become the thing.

For teams that want flexible theming without turning every variation into a new modifier, pairing BEM with CSS variables in component systems is often cleaner than creating a long list of one-off variant classes.

When custom properties help

BEM isn’t a license to encode every design decision in class names. If you find yourself writing classes like .card--padding-large, .card--padding-small, .card--title-blue, and .card--title-gray, stop and reconsider.

Use modifiers for meaningful component variants or states. Use custom properties for values that should remain configurable.

.card {
  --card-border-color: #d9d9d9;
  --card-background: #fff;

  border: 1px solid var(--card-border-color);
  background: var(--card-background);
}

.card--featured {
  --card-border-color: #111;
}

That keeps the naming semantic. The modifier expresses intent. The custom property carries the style value.

A quick walkthrough can help if you want to see the syntax discussed visually:

Common BEM Anti-Patterns and How to Fix Them

Most BEM mistakes come from carrying old CSS habits into a new naming system. The syntax looks tidy, but the architecture underneath stays tangled.

An infographic illustrating BEM anti-patterns and best practices for CSS naming conventions to improve code maintainability.

The nested element trap

The most common error is inventing element depth inside the class name:

.card__header__title { }

That looks descriptive, but it breaks a core BEM rule. The official BEM naming convention enforces a flattened DOM structure where element names never reflect nested depth, and a class like .block__elem1__elem2 is strictly invalid, according to the BEM naming convention documentation.

Why this rule matters is more important than the rule itself. As soon as the class name encodes hierarchy, the component becomes coupled to a specific DOM tree. Reordering markup becomes harder. Reusing the title in another wrapper becomes awkward.

Wrong

<div class="card">
  <div class="card__header">
    <h2 class="card__header__title">Title</h2>
  </div>
</div>

Right

<div class="card">
  <div class="card__header">
    <h2 class="card__title">Title</h2>
  </div>
</div>

Using modifiers for everything

Another trap is using modifiers for pure styling noise.

  • Bad fit: .button--margin-top-large
  • Better fit: spacing utility or layout rule outside the component
  • Good fit: .button--primary, .button--disabled, .button--loading

If a class describes placement in one screen rather than a variation of the component itself, it probably shouldn’t be a BEM modifier.

Review question: Does this modifier describe a version of the component, or just a one-off layout need?

Mixing BEM with high-specificity selectors

BEM works best when selectors stay flat. Problems return when developers mix BEM classes with tags, IDs, and deep ancestry.

Anti-pattern Why it hurts Better approach
#sidebar .card__title Raises specificity and couples styles to location .card__title or a block-level modifier
.page .content .card button Depends on structure, not component contract .card__button
button.card__button Ties style to a tag unnecessarily .card__button

Despite adopting BEM naming, many codebases regress because developers still write selectors as if the classes are optional hints. They’re not. In a healthy BEM codebase, the class is the contract.

Applying BEM in Modern Component Architectures

BEM fits component-driven work better than many older tutorials suggest. In practice, a block maps cleanly to a component boundary, and the internal elements map to named parts of that component. That works well in Vue, and it also works in headless Web Components where behavior and structure aren’t always rendered in one obvious template.

A professional developer interacting with a visual chart illustrating BEM naming conventions for web components.

BEM in Vue components

In Vue, the easiest mental model is this: one component, one block namespace.

A BaseButton.vue file naturally becomes .button. Its internal parts become .button__icon, .button__label, and .button__spinner. Props often map to modifiers.

<template>
  <button
    class="button"
    :class="{
      'button--primary': variant === 'primary',
      'button--disabled': disabled,
      'button--loading': loading
    }"
    :disabled="disabled || loading"
  >
    <span v-if="$slots.icon" class="button__icon">
      <slot name="icon" />
    </span>

    <span class="button__label">
      <slot />
    </span>

    <span v-if="loading" class="button__spinner" aria-hidden="true"></span>
  </button>
</template>

That’s clean because the Vue API and the CSS naming reinforce each other. The prop says what changed. The modifier class reflects the same change in markup.

For larger systems, BEM also complements component composition patterns because it gives each composed piece a stable naming boundary.

BEM in headless Web Components

Headless primitives change the styling situation. A custom element like <x-dialog> or <x-dropdown> may provide behavior, ARIA wiring, and keyboard handling, but leave the visible markup to you.

That doesn’t make BEM less useful. It makes the naming boundary more important.

A solid pattern is:

  • Treat the visible wrapper as the block
  • Name projected or slotted parts as elements
  • Represent state with modifiers when that state affects the rendered structure
<div class="dialog dialog--open">
  <div class="dialog__backdrop"></div>
  <section class="dialog__panel">
    <header class="dialog__header">
      <h2 class="dialog__title">Delete project</h2>
    </header>
    <div class="dialog__body">This action can't be undone.</div>
    <footer class="dialog__actions">
      <button class="dialog__button dialog__button--danger">Delete</button>
    </footer>
  </section>
</div>

This approach keeps the styling readable even when the underlying behavior comes from a framework-agnostic primitive.

BEM in AI-assisted UI workflows

A common failing of current BEM guidance often occurs. Teams are increasingly editing generated markup, accepting code suggestions, or using tools that rewrite component structure. That can make class naming drift fast.

Industry discussions on AI-edited interfaces in Vue and Tailwind communities report that 28% of developers flag BEM naming as “broken” or “unpredictable” in AI-assisted workflows, according to Håvard Brynjulfsen’s discussion of BEM struggles.

The issue usually isn’t BEM itself. It’s inconsistency in how tools generate parts, states, and wrappers. When naming isn’t governed, AI tends to produce classes that mix role, appearance, and DOM depth.

A practical response is to make naming rules explicit:

  1. Map each component to one block name
  2. Allow only one element level
  3. Reserve modifiers for state or meaningful variants
  4. Keep utility classes separate from structural names

If you’re working with AI-edited interfaces, BEM becomes less of a CSS preference and more of a machine-readable contract.

Combining BEM with Tailwind CSS for Hybrid Styling

BEM and Tailwind aren’t opponents. They solve different problems. BEM gives structure and naming. Tailwind gives fast, low-level styling primitives.

Give each tool a job

Use BEM to answer, “What is this thing?”

Use Tailwind to answer, “How does it look right now?”

That split works well in production because it avoids semantic drift. You don’t want a class like .rounded-large-blue-card-with-shadow trying to replace component architecture. You also don’t want a giant utility string to become the only description of a complex UI block.

A practical hybrid model looks like this:

  • BEM for component identity: .product-card, .product-card__title, .product-card--featured
  • Tailwind for implementation detail: spacing, color, border radius, layout, responsive adjustments
  • Theme coordination through system tokens: especially when your design team needs consistent variants across products

For teams thinking about theming, Tailwind CSS themes in component systems is the relevant layer to pair with BEM naming.

A practical hybrid pattern

One clean option is to keep BEM in the markup and group Tailwind utilities in component CSS.

<article class="product-card product-card--featured">
  <h2 class="product-card__title">Travel Pack</h2>
  <p class="product-card__price">$89</p>
</article>
.product-card {
  @apply rounded-xl border bg-white p-4;
}

.product-card--featured {
  @apply ring-2 ring-black;
}

.product-card__title {
  @apply text-lg font-semibold;
}

.product-card__price {
  @apply mt-2 text-sm text-slate-600;
}

Another option is to keep the structural BEM classes and sprinkle utilities only for local layout needs in composition points. That’s often a good compromise in Vue templates where parent components manage spacing while child components keep a stable BEM contract.

The mistake isn’t mixing BEM with utilities. The mistake is letting the two systems describe the same responsibility.

Conclusion From Naming Rules to Production Strategy

The BEM naming convention isn’t just a formatting preference. It’s a way to keep component styling understandable when a codebase grows, teams expand, and markup becomes more dynamic.

Used well, BEM gives you three things that matter in production: clear ownership, flat selectors, and names that survive refactors. That remains valuable whether you’re working in plain CSS, Vue single-file components, headless custom elements, or a hybrid stack that also uses Tailwind.

The best teams don’t treat BEM as dogma. They use it as a boundary system. Blocks define components. Elements define parts. Modifiers define meaningful change. Everything else stays outside that contract.

As interfaces become more composable and more machine-generated, readable class architecture becomes more important, not less.


If you’re building Vue apps or AI-ready interfaces with headless primitives, DOM Studio is worth a look. It gives teams a practical foundation for accessible, composable UI, with Web Components, Vue wrappers, and Tailwind-friendly workflows that fit the kind of component architecture BEM was always meant to support.