The Ultimate Guide to BEM: From Basics to Advanced Techniques

Blog

Posted by Nuno Marques on 9 Nov 2024

CSS management can get chaotic as your projects grow, with cascading rules, specificity wars, and hard-to-maintain styles. The BEM (Block, Element, Modifier) methodology is a proven approach to create scalable, maintainable, and reusable CSS. In this guide, we’ll start with the basics and gradually progress to advanced techniques for senior developers, ensuring there’s something for everyone.


What is BEM?

BEM stands for:

  • Block: An independent and reusable component (e.g., button, card).
  • Element: A dependent part of a block (e.g., button__icon, card__title).
  • Modifier: A variation or state of a block or element (e.g., button--disabled, card--highlighted).

Syntax Overview

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

Why Use BEM?

BEM simplifies CSS by providing:

  1. Clarity: Clear relationships between blocks, elements, and modifiers.
  2. Scalability: Easily extend styles without conflicts.
  3. Reusability: Modular design promotes consistent components.
  4. Maintainability: Well-structured naming reduces debugging time.

Basics of BEM: A Beginner’s Walkthrough

Here are simple implementations of a .button and .card components using BEM:

Example 1: A Simple Button

HTML

<button class="button button--primary">
  <span class="button__icon">🚀</span>
  <span class="button__label">Launch</span>
</button>

CSS

/* Block */
.button {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

/* Element */
.button__icon {
  margin-right: 8px;
}

/* Modifier */
.button--primary {
  background-color: blue;
  color: white;
}

Example 2: A Highlighted Card

HTML

<div class="card card--highlighted">
  <h3 class="card__title">Card Title</h3>
  <p class="card__description">This is a card description.</p>
</div>

CSS

/* Block */
.card {
  padding: 16px;
  border: 1px solid #ddd;
}

/* Element */
.card__title {
  font-size: 1.5rem;
}

.card__description {
  color: #666;
}

/* Modifier */
.card--highlighted {
  background-color: #fff7e6;
  border-color: orange;
}

Intermediate Concepts: Dynamic States and Multiple Modifiers

As your project grows, you’ll encounter more dynamic requirements, such as managing states and combining modifiers.

Example: Card with Multiple Modifiers

HTML

<div class="card card--shadow card--rounded card--compact">
  <img src="image.jpg" alt="Thumbnail" class="card__image" />
  <h3 class="card__title">Card Title</h3>
  <p class="card__description">Short description of the card.</p>
</div>

CSS

/* Block */
.card {
    display: flex;
    flex-direction: column;
    padding: 16px;
    border: 1px solid #ddd;
}

/* Modifiers */
.card--shadow {
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.card--rounded {
    border-radius: 8px;
}

.card--compact {
    padding: 8px;
    flex-direction: row;
}

/* Elements */
.card__image {
    margin-bottom: 8px;
    width: 100%;
}

.card__title {
    font-size: 1.25rem;
}

.card__description {
    font-size: 1rem;
    color: #555;
}

Key Takeaways

  • Use multiple modifiers (--shadow, --rounded) to create reusable components.
  • Avoid deeply nested selectors; instead, compose styles with flexible modifiers.

Advanced BEM for Senior Developers

Advanced projects demand more robust techniques, like state classes, nested structures, and integration with utility classes or frameworks.


Dynamic State Classes: .is-* and .has-*

  • .is-*: Reflects a state of the block or element, such as is-active, is-loading.
  • .has-*: Indicates that a block or element contains something, such as has-error.

Example: A Button with Loading State

HTML

<button class="button button--primary is-loading">
  <span class="button__icon">⏳</span>
  <span class="button__label">Loading...</span>
</button>

CSS

/* State */
.is-loading .button__icon {
  animation: spin 1s linear infinite;
}

.is-loading .button__label {
  opacity: 0.5;
}

@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

Nested Structures: Complex Components

Example: Table with Elements and Modifiers

HTML

<table class="table">
  <thead class="table__head">
    <tr class="table__row">
      <th class="table__cell table__cell--header">Name</th>
      <th class="table__cell table__cell--header">Age</th>
    </tr>
  </thead>
  <tbody class="table__body">
    <tr class="table__row">
      <td class="table__cell">John Doe</td>
      <td class="table__cell">29</td>
    </tr>
  </tbody>
</table>

CSS

/* Block */
.table {
  width: 100%;
  border-collapse: collapse;
}

/* Element */
.table__row {
  border-bottom: 1px solid #ddd;
}

.table__cell {
  padding: 8px;
}

/* Modifier */
.table__cell--header {
  font-weight: bold;
  background-color: #f5f5f5;
}

Combining BEM with Utility Classes

When using utility-first frameworks like Tailwind CSS, mix utility classes with BEM for maximum flexibility.

HTML

<div class="card card--shadow">
  <h3 class="card__title text-lg font-bold">Card Title</h3>
  <p class="card__description text-sm text-gray-600">Card description</p>
</div>=

Key Takeaways

  • Use utility classes for non-specific styles (e.g., spacing or typography).
  • Keep BEM classes for reusable components and structure.

Dynamic BEM with JavaScript Frameworks

In modern React or Vue applications, dynamically apply BEM classes to handle interactivity.

Example: React Button Component

function Button({ isLoading, isDisabled }) {
const buttonClass = `button ${isLoading ? "is-loading" : ""} ${
    isDisabled ? "is-disabled" : ""
  }`;

return <button className={buttonClass}>Click Me</button>;
}

Common Pitfalls and Solutions

  1. Deep Nesting: Avoid .block__element__child__grandchild. Refactor into separate blocks if needed. .
  2. Think Reusability: Keep blocks generic enough for multiple contexts.
  3. Leverage Modifiers: Keep modifiers focused, e.g., --large and --primary, not --large-primary-blue.
  4. Keep States Clear: Use .is- for dynamic states and .has- for conditional child elements.

Key Benefits of Advanced BEM

  • Team Collaboration: Consistent patterns reduce onboarding time for new team members.
  • Scalable Architecture: Supports growth without CSS conflicts.
  • Maintainable Code: Easier debugging and updates.

Further Resources


Conclusion

From simple modular components to complex state-driven UI, BEM can scale with your project. Beginners can start with its structured syntax, while advanced users can leverage dynamic states, nested components, and integrations with utility classes or frameworks. Whether you're working on a simple website or a large-scale application, BEM ensures your CSS stays clean, predictable, and maintainable.