CSS Naming Conventions: BEM

What You Learn From This Page

  • What Block, Element, and Modifier mean and how their class syntax communicates ownership.
  • How to choose component boundaries and distinguish an element, nested block, modifier, or utility.
  • Why BEM favors semantic names, single-class selectors, flat specificity, and markup-independent styling.
  • How BEM works with native HTML states, ARIA, custom properties, cascade layers, themes, and responsive CSS.
  • How to avoid chained elements, modifier-only markup, visual names, excessive variants, and deep selectors.
  • How to adopt BEM in real projects without coupling styling classes to JavaScript or automated tests.

Introduction

BEM stands for Block, Element, Modifier. It is a CSS class-naming methodology that gives every reusable interface component a recognizable vocabulary. A block names the independent component, an element names a meaningful part owned by that component, and a modifier names a supported variation of the block or element. The familiar forms are .card, .card__title, and .card--featured.

BEM does not add a browser feature or change how the cascade works. It is a team agreement expressed through class names. That agreement makes selectors predictable, reduces naming collisions, lowers dependence on exact HTML structure, and helps developers discover where a rule belongs. Its value becomes most visible after a codebase contains many components maintained by many people.

The method should be used as an aid to clarity rather than a mechanical requirement. Semantic HTML, design tokens, cascade layers, utilities, media queries, and modern component systems solve different problems. BEM works best when it cooperates with those tools and remains focused on one responsibility: communicating component ownership and variation.

Why CSS Naming Conventions Matter

A small prototype can survive names such as .box, .blue, .special, or .new-style. In a production application, those names reveal neither purpose nor ownership. A developer cannot tell whether .title belongs to a card, dialog, article, or navigation panel, and changing it may affect unrelated screens.

A consistent naming convention turns class names into documentation. It gives contributors a shared way to create components, makes code search more useful, reduces accidental global rules, and shortens onboarding. The convention does not guarantee good architecture, but it removes avoidable ambiguity from everyday styling decisions.

The Basic BEM Pattern

The basic pattern is compact:

.card {}
.card__title {}
.card__body {}
.card--featured {}

card is the block. title and body are elements owned by that block. featured is a modifier that changes the card from its default presentation. The double underscore separates a block from an element, while the double hyphen separates a block or element from a modifier.

What Is a Block?

A block is an independent, reusable component with a purpose that makes sense outside one page position. Examples include .button, .card, .alert, .modal, .pagination, and .product-card. A block owns its internal appearance and behavior contract, but it should not assume an exact parent or screen.

<article class="card">
  ...
</article>

The corresponding .card rule can establish padding, background, border, and internal layout. Placement in a dashboard grid or article column belongs to the surrounding layout rather than the block itself.

Name Blocks by Purpose

Good block names describe what the component represents: product-card, account-menu, search-form, or status-alert. Names such as left-box, blue-area, big-div, and section-2 describe a temporary appearance or location. They become misleading when design or placement changes.

Purpose-based names also encourage reuse. A .button can appear in a footer, toolbar, modal, or card without acquiring a new identity. If those contexts require genuinely supported variants, modifiers or contextual tokens can express the difference.

Avoid Page-Specific Blocks Without a Reason

A name such as .home-page-main-card can trap a generally useful component inside one route. Before adding a page name, ask whether the same structure and behavior could appear elsewhere. If so, .card, .article-card, or .product-card is probably a better boundary.

Page-specific blocks are valid when the component truly represents a unique page region and has no reusable contract. BEM does not require pretending every fragment is universal; it asks teams to make that decision consciously.

What Is an Element?

An element is a meaningful part that belongs to a block and normally does not make sense as an independent component. It uses the form block__element. A card may own .card__image, .card__title, .card__description, and .card__actions.

<article class="card">
  <h2 class="card__title">CSS Guide</h2>
  <p class="card__description">Learn scalable CSS.</p>
</article>

The names tell readers that both nodes are part of the card API. They are more explicit than generic classes such as .title and .description, and they do not depend on the heading or paragraph tags remaining unchanged.

Why the Double Underscore Helps

The double underscore is a visual ownership marker. When developers encounter .checkout-form__error, they immediately know that the error belongs to the checkout form block. Searching for checkout-form__ reveals the component's elements, while searching for checkout-form-- reveals its modifiers.

The separator itself is not magical. Teams can choose another documented BEM dialect, but mixing underscores, hyphens, and unrelated patterns removes the predictability the convention is meant to provide.

Elements Describe Ownership, Not DOM Depth

A frequent mistake is to encode every nesting level:

/* Avoid */
.card__header__title {}

/* Prefer */
.card__header {}
.card__title {}

BEM elements refer directly to their block. A title can sit inside a header today and move into the card body tomorrow without requiring a class rename. This flat ownership model reduces structural coupling and keeps refactoring focused on actual behavior.

Block or Element?

Ask whether the part can make sense, carry its own contract, or be reused independently. If yes, it is likely a block. A generic application button placed inside .card__actions should usually remain the .button block rather than becoming .card__button. The card owns the action region; the button owns its own visual and interaction behavior.

If the part is meaningful only within the parent component, an element is appropriate. A card title, modal body, or navigation list usually belongs directly to its block.

Nested Blocks Are Normal

BEM permits independent blocks inside other blocks. A card can contain a button, badge, price display, or rating component without absorbing those components into its naming system.

<article class="product-card">
  <div class="product-card__actions">
    <button class="button button--primary">Buy</button>
  </div>
</article>

This composition keeps each block reusable. The parent arranges the action area, while the button defines its own padding, states, icon spacing, and variants.

What Is a Modifier?

A modifier represents a supported variation, version, or mode of a block or element. It uses block--modifier or block__element--modifier. Examples include .button--danger, .card--compact, and .nav__link--active.

A modifier should describe only what differs from the base component. It is not a second complete implementation. This keeps common behavior centralized and makes variants easy to compare.

Use the Base Class with the Modifier

Modifier markup normally includes both classes:

<button class="button button--danger">Delete</button>

.button supplies shared layout, typography, border radius, and interaction behavior. .button--danger supplies the meaningful differences. Using only .button--danger forces the modifier to duplicate the base or depend on an undocumented selector.

Keep Modifiers Focused

.button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
}

.button--danger {
  background: var(--color-danger);
  color: var(--color-on-danger);
}

If the danger rule repeats all base declarations, changes to ordinary button spacing will drift from the danger version. Focused modifiers preserve one source of truth.

Block Modifiers and Element Modifiers

A block modifier changes the component as a whole. .card--featured may change border emphasis, while .modal--fullscreen changes the modal's size contract. An element modifier changes one owned part, such as .nav__link--active.

Choose the level that reflects the actual variation. Do not attach an element modifier when the entire component changes, and do not create a block modifier for a small state limited to one element.

Name Modifiers Semantically

Prefer .button--primary, .button--danger, .alert--success, and .card--featured over .button--blue, .alert--green, and .card--yellow. Semantic names survive a redesign in which the visual color changes but the product meaning does not.

Size variants such as .button--small and .button--large are also reasonable when they are deliberate component options. Avoid implementation names such as .button--42px.

Boolean and Key-Value Modifier Styles

A boolean name such as .card--featured expresses that a property is present. Larger systems sometimes adopt key-value forms such as .button--size-large or .button--tone-danger. These can clarify which variant axes are supported and prevent ambiguous names.

There is no universally mandatory dialect. Select a syntax that fits the team, document examples, and apply it consistently throughout the codebase.

Use Native HTML States First

BEM modifiers should not duplicate state already represented by HTML. A disabled button should carry the native disabled attribute and use .button:disabled, rather than relying only on .button--disabled. The attribute supplies browser behavior, accessibility semantics, form behavior, and a reliable styling hook.

<button class="button" disabled>Saving</button>

.button:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

Use ARIA State as a Styling Hook

Interactive components often expose state through ARIA. An accordion trigger can be styled with [aria-expanded="true"], and a tab can use [aria-selected="true"]. This keeps the visual state synchronized with the information assistive technology receives.

.accordion__trigger[aria-expanded="true"] {
  font-weight: 700;
}

.accordion__trigger[aria-expanded="true"] .accordion__icon {
  rotate: 180deg;
}

A modifier remains appropriate for a design variant that has no native semantic equivalent.

BEM and Predictable Specificity

BEM encourages single-class selectors such as .card, .card__title, and .card--featured. These selectors have the same low specificity, so source order and documented cascade layers can resolve intentional differences without escalation.

Contrast that with #app main .page .card h2. The long selector is tied to an ID, element type, and hierarchy. A simple component variant cannot easily override it. Flat specificity is one of BEM's most valuable maintenance outcomes.

BEM Instead of Fragile Descendant Selectors

A selector such as .card h2 styles every second-level heading under a card, including nested components added later. .card__title targets the role directly and continues to work if the heading level changes for document hierarchy.

BEM is not permission to add unnecessary classes to every node. Use classes for meaningful component roles, and allow inheritance or simple internal selectors where they are genuinely clearer and safe.

Avoid Deep Selectors with BEM Names

/* Defeats the purpose of BEM */
.page .card .card__header .card__title {}

/* Ownership is already explicit */
.card__title {}

Combining BEM names with deep context selectors keeps the long names while losing portability and low specificity. Scope through the class itself and use an explicit variant or token when the component has a supported contextual difference.

Avoid IDs for Component Styling

IDs are unique document hooks with high specificity. Component CSS usually needs reuse and easy variation, so classes are a better fit. Use .card instead of #main-card, and reserve IDs for relationships such as labels, descriptions, fragments, or script APIs that specifically require them.

BEM and CSS Nesting

Native CSS nesting can group state selectors near a block:

.button {
  &:hover { background: var(--button-hover); }
  &:focus-visible { outline: 0.2rem solid var(--focus); }
  &.button--danger { --button-bg: var(--color-danger); }
}

However, BEM classes are already explicit. Nesting is optional, and deep nested selectors reintroduce structural coupling. Native nesting also should not be assumed to reproduce every Sass string-concatenation pattern; explicit selectors remain clear and portable.

BEM and Cascade Layers

BEM controls naming, while cascade layers control authority. The two complement each other:

@layer reset, base, components, utilities;

@layer components {
  .card {}
  .card__title {}
  .card--featured {}
}

A documented layer order prevents a naming convention from carrying responsibilities it cannot solve. Vendor styles, resets, components, and utilities can remain predictably ordered even when their selector specificity differs.

BEM and Custom Properties

Custom properties let a modifier change component tokens instead of repeating final declarations:

.button {
  --button-bg: var(--color-primary);
  --button-border: var(--color-primary-strong);
  background: var(--button-bg);
  border-color: var(--button-border);
}

.button--danger {
  --button-bg: var(--color-danger);
  --button-border: var(--color-danger-strong);
}

The base remains responsible for consuming values, while the modifier declares a supported configuration. This pattern scales well across hover, disabled, and high-contrast states.

BEM Does Not Replace Design Tokens

BEM names structural roles. Design tokens name reusable values such as colors, spacing, typography, radius, elevation, and motion. A well-designed component uses both: BEM classes identify the component API, and tokens provide consistent design decisions.

Do not encode every value into a modifier. A token such as --space-md is more appropriate than creating .card--padding-16 throughout a product.

BEM and Global Themes

Creating .card--dark, .button--dark, and .input--dark duplicates a whole-site concern across every block. Prefer a theme scope that changes semantic tokens:

[data-theme="dark"] {
  --color-surface: #161a20;
  --color-text: #f4f7fa;
  --color-border: #66717f;
}

Components continue to consume the same tokens. BEM modifiers remain available for component-level variants such as primary, compact, or featured.

BEM and Responsive Design

A viewport is not usually a semantic component variant. Avoid names such as .card--mobile and .card--desktop. Let the component adapt through intrinsic layout, media queries, or container queries.

.product-card { container-type: inline-size; }

@container (min-width: 32rem) {
  .product-card__body {
    display: grid;
    grid-template-columns: 10rem 1fr;
  }
}

Use .product-card--horizontal only when horizontal presentation is an explicit application choice rather than an automatic response to available space.

BEM and Utility Classes

BEM components and utility classes can coexist. .card communicates a component contract, while .text-center or .visually-hidden applies one focused behavior. A utility should not be disguised as an element such as .card__text-center.

Define whether utilities may override component styles and encode that policy through layer order. Without a policy, utility composition can become another source of accidental conflicts.

BEM Does Not Mean Everything Is a Class

Global document defaults may still target body, headings, links, form controls, or semantic regions. Pseudo-classes, attributes, and inheritance remain useful. BEM is most valuable for reusable components, not as a requirement to rename the entire HTML language.

Complete Card Example

<article class="card card--featured">
  <img class="card__image" src="course.webp" alt="CSS course cover">
  <div class="card__body">
    <h2 class="card__title">Modern CSS</h2>
    <p class="card__description">Build resilient interfaces.</p>
  </div>
  <div class="card__actions">
    <a class="button button--primary" href="course.html">Study</a>
  </div>
</article>

The markup communicates structure without exposing a DOM-specific selector chain. The card and button remain separate blocks, while the featured modifier identifies one supported card variation.

Button Component Example

<button class="button button--primary" type="button">
  <span class="button__icon" aria-hidden="true">+</span>
  <span class="button__label">Add item</span>
</button>

The icon and label are elements because they belong to the button's internal structure. Primary and danger are modifiers because they express action hierarchy. Hover, focus, active, and disabled remain native state selectors.

Navigation Component Example

<nav class="main-nav" aria-label="Primary">
  <ul class="main-nav__list">
    <li class="main-nav__item">
      <a class="main-nav__link main-nav__link--active"
         href="index.html" aria-current="page">Home</a>
    </li>
  </ul>
</nav>

aria-current="page" should be the semantic source of truth, so an implementation may style that attribute directly instead of adding the active modifier. Whichever policy the team chooses, avoid allowing the class and accessibility state to disagree.

Alert Component Example

.alert {
  --alert-color: var(--color-info);
  padding: 1rem;
  border: 1px solid var(--alert-color);
}

.alert__title { font-weight: 700; }
.alert__message { margin-block-start: 0.5rem; }
.alert--success { --alert-color: var(--color-success); }
.alert--error { --alert-color: var(--color-danger); }

The variants override one semantic component token. The base uses that value consistently for borders, icons, and other emphasis. Status text and suitable semantics must accompany color so the alert remains understandable to all users.

Modal and Form Boundaries

A modal may own .modal__header, .modal__title, .modal__body, .modal__footer, and .modal__close, with variants such as .modal--large. A form may own groups, actions, and summary regions. Reusable input, select, checkbox, and button controls may deserve independent blocks rather than becoming elements of every form.

Component boundaries should follow reuse and responsibility, not merely the visual outline visible in one screenshot.

Avoid Modifier Explosion

A component with modifiers for every color, shadow, alignment, width, radius, margin, and page location has become a configuration dumping ground. Some differences belong in tokens, some in utilities, some in the parent layout, and some indicate that two distinct components exist.

Keep the public variant API small and purposeful. Every modifier creates combinations that may need documentation, accessibility review, responsive testing, and long-term support.

Avoid Excessively Long Names

.product-card__description is longer than .desc, but its global meaning is clearer. Length becomes a warning when a name resembles .home-page-featured-product-card__main-title-text. That usually indicates the wrong block boundary or an attempt to encode ancestry.

Avoid unexplained abbreviations such as .crd__ttl. Saving a few characters is rarely worth reducing searchability and onboarding clarity.

Separate Styling from JavaScript Hooks

JavaScript that queries .button--primary silently couples behavior to visual terminology. If a design change renames the modifier to .button--brand, behavior may break. Prefer a behavioral hook such as data-action="save", an ID used by an explicit API, or a framework reference.

A class can serve both purposes in a small controlled component, but the coupling should be deliberate and documented rather than accidental.

Separate Styling from Test Hooks

Automated tests should select elements through user-facing roles and accessible names when that reflects behavior. Where a stable technical hook is required, use an agreed data-test or data-testid attribute. Cosmetic class changes should not break unrelated behavior tests.

Visual regression tests may legitimately care about BEM classes or rendered appearance. Match the selector strategy to the type of evidence the test needs.

Common BEM Mistakes

MistakeBetter ApproachReason
.card__header__title.card__titleElements belong directly to the block.
Modifier without base classbutton button--dangerThe base owns shared behavior.
.button--blue.button--primaryMeaning survives visual redesign.
.button--footer.button--secondaryLocation is not a stable variant.
.card--mobileContainer or media queryViewport size is responsive context.
.card--dark everywhereTheme tokensTheme is a system-level concern.
Deep BEM selector chainSingle component classPreserves low specificity.
Reusable button as card elementNested .button blockIndependent blocks retain reuse.

BEM Structure Reference

TypeSyntaxExamplePurpose
Blockblock.cardIndependent component
Elementblock__element.card__titlePart owned by a block
Block modifierblock--modifier.card--featuredBlock variation
Element modifierblock__element--modifier.nav__link--activeElement variation

A Practical BEM Decision Process

Begin by identifying the reusable component. That is the block. For each internal part, ask whether it is meaningful only within that component; if so, it is an element. If a part has its own reusable contract, make it a nested block. Finally, ask whether a difference is a deliberate supported variation, a native state, a responsive adaptation, a utility, or a theme concern.

This sequence prevents naming from starting with punctuation. Architecture comes first; the separators simply record the ownership decision.

Adopting BEM in an Existing Project

Do not rename an entire application in one risky release. Start with new or frequently changed components. Document the chosen dialect, modifier policy, native-state policy, utility interaction, and examples. Add linting where it provides reliable guidance.

When migrating an old component, add regression coverage, define the block boundary, replace generic or structural selectors, update markup and CSS together, and remove obsolete rules. Avoid keeping permanent aliases unless external consumers require them.

Treat BEM Classes as a Component API

A reusable component's class names form an interface between markup and CSS. Renaming .profile-card__avatar, removing .profile-card--compact, or changing the meaning of .profile-card__actions can affect templates, content systems, consuming applications, documentation, and visual tests. Teams should therefore review public class changes with the same care applied to a function signature.

Document which blocks and modifiers consumers may use. Internal implementation elements do not all need to become permanent extension points. When another feature needs a variation, decide whether the existing component should officially support it, whether a contextual custom property is sufficient, or whether a separate composition is clearer. This prevents external code from depending on accidental internals.

A component example or small visual catalog can show the base block, supported modifiers, native states, content limits, nested blocks, and responsive behavior. This documentation turns BEM from punctuation rules into an understandable design contract. It also exposes unsupported combinations early. For example, a compact card with a long translated title, an error status, and two actions may reveal layout assumptions that ordinary examples miss.

When a public class must change, migrate consumers deliberately. Introduce the replacement, update templates and tests, communicate the change, and remove the old selector after the agreed compatibility period. Avoid leaving two permanent names that control the same component because parallel APIs create uncertainty about which one future work should use.

When BEM Is a Good Fit

BEM is useful for shared stylesheets, multi-page sites, design systems, server-rendered applications, and teams that need class names to communicate scope. It is especially valuable where selectors otherwise become global and developers regularly search across a large codebase.

Its explicit names also work well with component frameworks when global CSS or interoperable markup remains important.

When BEM May Be Unnecessary

A tiny document, a strictly scoped single-file component, CSS Modules with generated local names, or a utility-first system may already provide collision control. Even there, the conceptual questions behind BEM remain useful: what owns this style, what is reusable, and what represents a supported variant?

Do not combine every methodology at maximum intensity. Select the smallest set of conventions that gives the team predictable ownership and safe change.

Common Interview Questions

What does BEM stand for? Block, Element, Modifier. A block is an independent component, an element is a part owned by that block, and a modifier is a supported variation.

Why does BEM improve maintainability? It communicates ownership, reduces class collisions, encourages low-specificity selectors, minimizes dependence on DOM hierarchy, and gives teams a predictable naming vocabulary.

Should BEM elements be chained? Generally no. Use .card__title rather than .card__header__title because elements refer directly to their block.

Should a modifier appear without its base class? Usually no. The base owns shared component styling, while the modifier supplies only differences.

Should disabled or expanded states always use modifiers? No. Prefer native pseudo-classes and semantic attributes such as :disabled, aria-expanded, and aria-selected when they already represent the state.

Does BEM replace cascade layers or design tokens? No. BEM organizes names, layers organize priority, and tokens organize reusable design values.

Can BEM coexist with utility classes? Yes. BEM describes component structure; utilities apply small reusable behaviors. A documented layer policy should define how they interact.

How do you decide between an element and nested block? If the part has an independent reusable contract, treat it as a block. If it makes sense only as part of the parent, treat it as an element.

Practical Review Checklist

  • Identify the reusable block before naming its internal parts.
  • Name blocks and modifiers by purpose rather than appearance or location.
  • Use block__element for parts that belong directly to a block.
  • Use block--modifier for meaningful supported variations.
  • Keep the base class alongside its modifier in markup.
  • Keep selectors mostly single-class and low-specificity.
  • Avoid chained elements such as block__element__child.
  • Use independent nested blocks for reusable controls and components.
  • Prefer native HTML and ARIA state selectors where they express real state.
  • Use custom properties for configurable values and themes.
  • Use media or container queries for responsive adaptation.
  • Keep utilities, design tokens, cascade layers, and BEM responsibilities distinct.
  • Separate styling classes from JavaScript and automated-test hooks when useful.
  • Limit modifier combinations and document the supported component API.
  • Choose one BEM dialect and apply it consistently across the team.
  • Use BEM where it adds clarity rather than applying it mechanically.

Key Takeaway

BEM gives CSS classes a readable ownership model. A block identifies a reusable component, an element identifies a part owned by that component, and a modifier identifies a deliberate variation. The resulting single-class selectors reduce collisions, keep specificity flat, and allow markup to evolve without forcing selectors to mirror every nesting level.

The strongest BEM implementations remain semantic and pragmatic. They use native states for native behavior, independent blocks for reusable parts, tokens for values, layers for cascade authority, themes for system-wide modes, and responsive queries for available space. Adopt one consistent dialect, keep variants focused, and treat the class names as a small public API that future developers can understand and change safely.