Mastering Class Naming Conventions: A Deep Dive into BEM

You've spent hours crafting the perfect CSS for your project, only to watch it devolve into a tangled mess of conflicting styles and specificity wars as your application grows. Class names start to collide, overrides become increasingly complex, and your once-elegant stylesheet now resembles spaghetti code that no one wants to touch.

"Shouldn't there be a better way to organize CSS in modern web development?" you wonder, as you reluctantly add yet another !important flag to solve a styling conflict.

Enter BEM—a methodology that promises to bring order to CSS chaos through structured naming conventions. But in an ecosystem filled with CSS-in-JS, CSS Modules, and utility-first frameworks like Tailwind, does BEM still have a place? And if so, how can it complement these modern approaches?

Understanding BEM: The Building Blocks

BEM stands for Block Element Modifier, a naming convention created by Yandex that helps developers create reusable components and organize CSS code in a logical, maintainable way.

The methodology breaks down UI components into three distinct parts:

  • Block: A standalone component that is meaningful on its own (e.g., .card, .nav, .button)

  • Element: A part of a block that has no standalone meaning (e.g., .card__title, .nav__item)

  • Modifier: A flag that changes the appearance or behavior of a block or element (e.g., .button--large, .nav__item--active)

The naming convention follows this pattern:

.block {}
.block__element {}
.block--modifier {}
.block__element--modifier {}

This structured approach creates a clear relationship between your HTML and CSS, making your code more predictable and easier to maintain as projects scale.

A Simple BEM Example

Here's how a card component might look using BEM naming conventions:

<div class="card">
  <div class="card__header">
    <h2 class="card__title">Card Title</h2>
  </div>
  <div class="card__body">
    <p class="card__text">Lorem ipsum dolor sit amet.</p>
  </div>
  <div class="card__footer card__footer--highlighted">
    <button class="card__button">Click me</button>
  </div>
</div>

The corresponding CSS would be:

.card { /* styles for the card block */ }
.card__header { /* styles for the header element */ }
.card__title { /* styles for the title element */ }
.card__body { /* styles for the body element */ }
.card__text { /* styles for the text element */ }
.card__footer { /* styles for the footer element */ }
.card__footer--highlighted { /* modifier styles for the highlighted footer */ }
.card__button { /* styles for the button element */ }

This approach makes it immediately clear which elements belong to the card component and how they relate to each other, creating a self-documenting structure that's invaluable in larger projects.

Why BEM Still Matters in Modern Front-End Development

With the rise of component-based frameworks and CSS-in-JS solutions, you might wonder if BEM is still relevant. After all, many modern approaches already solve the problem of CSS scope—so why add another layer of complexity?

As one developer on Reddit noted, "I thought everyone was using BEM cause they told us it was 'industry standard'. I couldn't believe it." This sentiment reflects the confusion many feel about where BEM fits in today's ecosystem.

The truth is, BEM continues to offer significant advantages even in modern development environments:

1. Improved Code Readability and Maintainability

BEM creates a clear visual hierarchy in your code. When you see .card__footer--highlighted, you immediately understand that this is a highlighted version of the footer element within the card block. This self-documenting nature makes your code more approachable for new team members and your future self.

2. Reduced Specificity Issues

BEM's flat structure helps avoid specificity wars. By using single-class selectors instead of nested selectors, you maintain a consistent level of specificity throughout your stylesheet, making it easier to override styles when needed without resorting to !important.

3. Enhanced Component Reusability

The modular nature of BEM makes components highly reusable. Because blocks are independent entities, you can move them between projects with minimal adjustments, saving development time and ensuring consistency.

4. Better Team Collaboration

On larger teams where multiple developers work on the same codebase, BEM provides a shared language and structure that reduces conflicts and miscommunications. As one developer pointed out, "I think it probably has its uses on larger teams, where you have multiple people adding CSS."

Integrating BEM with CSS Modules

CSS Modules have gained popularity for their ability to scope styles locally to components, preventing class name collisions. But rather than making BEM obsolete, CSS Modules can actually complement the BEM methodology for enhanced organization.

Here's how to effectively combine them:

The Hybrid Approach

When using React with CSS Modules, you can maintain BEM's structural benefits while leveraging CSS Modules' scoping:

import styles from './Card.module.css';

function Card() {
  return (
    <div className={styles.card}>
      <div className={styles.card__header}>
        <h2 className={styles.card__title}>Card Title</h2>
      </div>
      <div className={styles.card__body}>
        <p className={styles.card__text}>Content here</p>
      </div>
      <div className={`${styles.card__footer} ${styles['card__footer--highlighted']}`}>
        <button className={styles.card__button}>Click me</button>
      </div>
    </div>
  );
}

In your CSS Module:

/* Card.module.css */
.card { /* base styles */ }
.card__header { /* header styles */ }
/* and so on... */

This approach gives you the best of both worlds: BEM's clear structure and CSS Modules' local scoping.

Streamlining with Helper Libraries

To reduce verbosity when combining BEM with CSS Modules, you can use helper libraries like @hexa-it/bem-modules. These tools provide utility functions that generate BEM class names while maintaining compatibility with CSS Modules:

import { createBem } from '@hexa-it/bem-modules';
import styles from './Card.module.css';

const bem = createBem('card', styles);

function Card() {
  return (
    <div className={bem()}>
      <div className={bem('header')}>
        <h2 className={bem('title')}>Card Title</h2>
      </div>
      <div className={bem('footer', { highlighted: true })}>
        <button className={bem('button')}>Click me</button>
      </div>
    </div>
  );
}

This approach maintains BEM's semantic structure while simplifying the syntax, addressing one of the common complaints about BEM: "The syntax is ugly though. That's my one complaint."

BEM and Tailwind: Finding the Balance

Tailwind CSS has gained massive popularity for its utility-first approach, allowing developers to style elements directly in HTML without writing custom CSS. However, this approach can lead to markup that feels cluttered and difficult to maintain.

As one developer expressed it: "I understand the upsides of tailwind, but having that many classes on my elements makes me want to barf."

So how can BEM and Tailwind coexist? The answer lies in strategic integration.

Component Extraction with BEM-Inspired Naming

Tailwind encourages extracting repeated patterns into components. When creating these components, you can apply BEM-inspired naming conventions to maintain clarity:

// Button.jsx
function Button({ size, variant, children }) {
  const baseClasses = "rounded focus:outline-none transition-colors";
  const sizeClasses = {
    small: "px-2 py-1 text-sm",
    medium: "px-4 py-2",
    large: "px-6 py-3 text-lg"
  };
  const variantClasses = {
    primary: "bg-blue-500 hover:bg-blue-600 text-white",
    secondary: "bg-gray-200 hover:bg-gray-300 text-gray-800"
  };
  
  return (
    <button className={`${baseClasses} ${sizeClasses[size]} ${variantClasses[variant]}`}>
      {children}
    </button>
  );
}

When using this component, you can think of it as a "block" in BEM terminology, with size and variant acting as "modifiers":

<Button size="small" variant="primary">Click me</Button>

Using Custom Properties with BEM and Tailwind

Another approach is to leverage CSS custom properties (variables) alongside Tailwind and BEM for a more flexible system:

/* Define variables using BEM-like structure */
:root {
  --card-padding: 1rem;
  --card-border-radius: 0.5rem;
  --card__header-background: theme('colors.gray.100');
  --card__footer--highlighted-background: theme('colors.blue.100');
}

.card {
  @apply p-4 rounded-lg;
  padding: var(--card-padding);
  border-radius: var(--card-border-radius);
}

.card__header {
  background-color: var(--card__header-background);
}

.card__footer--highlighted {
  background-color: var(--card__footer--highlighted-background);
}

This approach allows you to maintain the structure of BEM while leveraging Tailwind's utility classes and design tokens.

Class Variance Authority: A Modern Alternative

For those who find traditional BEM syntax cumbersome but still want structured component styling, Class Variance Authority (CVA) offers a compelling middle ground:

import { cva } from 'class-variance-authority';

const button = cva('button', {
  variants: {
    intent: {
      primary: 'bg-blue-500 text-white',
      secondary: 'bg-white text-gray-800 border border-gray-300',
    },
    size: {
      small: 'text-sm py-1 px-2',
      medium: 'text-base py-2 px-4',
      large: 'text-lg py-3 px-6',
    },
  },
  defaultVariants: {
    intent: 'primary',
    size: 'medium',
  },
});

function Button({ intent, size, className, ...props }) {
  return (
    <button className={button({ intent, size, className })} {...props} />
  );
}

CVA provides the structured approach of BEM with more flexible composition, making it easier to manage variants and combinations.

Best Practices for Using BEM Today

Based on community feedback and modern front-end trends, here are some practical tips for implementing BEM effectively:

1. Keep Block Names Short but Meaningful

Avoid overly long block names that make your classes unwieldy. Aim for concise but descriptive names that clearly identify the component.

2. Use Double Underscores and Hyphens Consistently

The distinction between __ for elements and -- for modifiers is crucial for readability. Maintain this consistency throughout your project.

3. Limit Nesting Depth

Avoid deep nesting of elements (e.g., .block__element1__element2). If you find yourself needing this structure, it might be time to create a new block.

4. Think in Components

Align your BEM structure with your component architecture. Each React, Vue, or Angular component often maps neatly to a BEM block.

5. Document Your Conventions

Create clear documentation for your team about how BEM is implemented in your project, especially if you're combining it with other methodologies like CSS Modules or Tailwind.

Conclusion

BEM remains a valuable methodology in modern front-end development, offering a structured approach to naming that enhances readability, maintainability, and collaboration. Rather than viewing it as competing with newer approaches like CSS Modules or Tailwind, consider how these methodologies can complement each other.

For smaller projects or solo developers, the full BEM syntax might feel excessive. As one developer noted, "I think it probably has its uses on larger teams, where you have multiple people adding CSS, but I've never really used it." This is a valid perspective—adapt BEM to your specific needs rather than following it dogmatically.

The most effective approach often combines multiple methodologies: use BEM for structural clarity, CSS Modules for scoping, and perhaps Tailwind for rapid prototyping and consistent design tokens. This hybrid approach gives you the strengths of each system while mitigating their individual weaknesses.

Ultimately, the goal is to create maintainable, scalable CSS that serves your project's needs and works well for your team. BEM provides a battle-tested framework for achieving this goal, even as front-end technologies continue to evolve.

Further Reading

Raymond Yeh

Raymond Yeh

Published on 22 April 2025

Choosing a CMS?

Wisp is the most delightful and intuitive way to manage content on your website. Integrate with any existing website within hours!

Choosing a CMS
Related Posts
Navigating the World of CSS: From CSS Modules to Tailwind

Navigating the World of CSS: From CSS Modules to Tailwind

Tired of CSS-in-JS headaches? Discover why developers are gravitating towards CSS Modules and Tailwind CSS. Learn how to choose and implement the right methodology for your team's needs.

Read Full Story
CSS in JS: The Good, the Bad, and the Future

CSS in JS: The Good, the Bad, and the Future

Compare CSS-in-JS vs CSS Modules performance and learn when to use each. Dive into zero-runtime solutions like Linaria and discover modern styling approaches for React applications.

Read Full Story
Best Practices for Using Tailwind CSS in Large Projects

Best Practices for Using Tailwind CSS in Large Projects

Tired of CSS chaos in large projects? Learn how to tame Tailwind's utility classes, maintain clean HTML, and scale effectively - without sacrificing the productivity boost you love.

Read Full Story
Loading...