Writing Efficient and Reusable CSS with BEM

Write efficient and reusable CSS using the BEM methodology. Enhance your workflow with structured, maintainable, and scalable styles.

In the world of web development, writing efficient and reusable CSS is a challenge that developers often face. One methodology that has gained popularity for tackling this issue is BEM, which stands for Block, Element, Modifier. BEM is a naming convention for classes in HTML and CSS that helps you create reusable components and code sharing in front-end development. This article will dive deep into the BEM methodology, exploring how it works, its benefits, and practical steps to implement it in your projects.

Understanding BEM

BEM stands for Block, Element, Modifier. It is a methodology developed by Yandex to improve the front-end code maintainability and reusability. The main idea behind BEM is to make the structure of HTML and CSS more predictable and easier to understand.

What is BEM?

BEM stands for Block, Element, Modifier. It is a methodology developed by Yandex to improve the front-end code maintainability and reusability. The main idea behind BEM is to make the structure of HTML and CSS more predictable and easier to understand.

By using a set naming convention, BEM helps developers and teams work more efficiently and reduces the chance of conflicts in styles.

The Basics of BEM

BEM divides the user interface into independent blocks, elements within those blocks, and modifiers that change the appearance or behavior of those blocks or elements. This division makes the code more modular and easier to manage.

  • Block: A block is a standalone entity that is meaningful on its own. It could be something like a button, a header, or a menu.
  • Element: An element is a part of a block that has no standalone meaning and is semantically tied to its block. Examples include a button label, an item in a menu, or an input field in a form.
  • Modifier: A modifier is a flag that changes the appearance or behavior of a block or element. Modifiers can represent different states or versions of a block or element, such as a disabled button or a highlighted menu item.

BEM Naming Conventions

The BEM naming convention uses a specific syntax to name blocks, elements, and modifiers. This syntax makes it clear which part of the component you are styling and how they relate to each other.

 

 

  • Block: .block
  • Element: .block__element
  • Modifier: .block--modifier or .block__element--modifier

For example, if you have a block called button, an element inside the button called icon, and a modifier for a large button, the class names would be:

  • Block: .button
  • Element: .button__icon
  • Modifier: .button--large

Benefits of Using BEM

Improved Readability and Maintainability

One of the primary benefits of using BEM is improved readability. By using a consistent naming convention, it becomes much easier to understand the structure and purpose of your HTML and CSS.

This readability makes maintaining the code simpler, as developers can quickly identify and modify components without guessing what class names mean or how they relate to each other.

Better Reusability

BEM promotes reusability by encouraging modular code. Since blocks are designed to be independent, they can be reused across different parts of a project or even in different projects.

This modularity saves time and effort, as you can build a library of reusable components that can be easily integrated and customized.

Avoidance of CSS Conflicts

CSS conflicts can be a significant issue in large projects, especially when multiple developers are working on the same codebase. BEM helps avoid these conflicts by using unique and descriptive class names that reduce the likelihood of naming collisions.

This isolation of styles ensures that changes to one component do not inadvertently affect others.

 

 

Easier Collaboration

When working in a team, having a clear and consistent methodology like BEM can significantly improve collaboration. Team members can easily understand each other’s code, making it easier to review, debug, and extend the codebase. This shared understanding leads to more efficient teamwork and faster development cycles.

Implementing BEM in Your Projects

To start using BEM, you need to set up your project structure to reflect the BEM methodology. This involves organizing your files and directories in a way that aligns with the BEM principles.

Setting Up Your Project Structure

To start using BEM, you need to set up your project structure to reflect the BEM methodology. This involves organizing your files and directories in a way that aligns with the BEM principles.

Example Project Structure

src/
|-- blocks/
|   |-- button/
|   |   |-- _large.scss
|   |   |-- _icon.scss
|   |   |-- _primary.scss
|   |   |-- button.scss
|-- elements/
|   |-- form/
|   |   |-- _input.scss
|   |   |-- _label.scss
|   |   |-- form.scss
|-- modifiers/
|   |-- _disabled.scss
|   |-- _highlighted.scss

Writing Your First Block

Start by defining a block. In this example, we will create a simple button block.

HTML

<button class="button">Click Me</button>

CSS

/* button.scss */
.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

.button__icon {
  margin-right: 5px;
}

.button--large {
  padding: 15px 30px;
}

Adding Elements and Modifiers

Next, add elements and modifiers to your block. For instance, if you want to add an icon inside your button and a large modifier, you can update your HTML and CSS accordingly.

Updated HTML

<button class="button button--large">
  <span class="button__icon">⭐</span>
  Click Me
</button>

Updated CSS

/* button.scss */
.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

.button__icon {
  margin-right: 5px;
}

.button--large {
  padding: 15px 30px;
}

Integrating with Preprocessors

Using CSS preprocessors like Sass or LESS can enhance your BEM workflow by providing additional functionality such as variables, nesting, and mixins. These features can help you manage your styles more efficiently and keep your code DRY (Don’t Repeat Yourself).

Example with Sass

/* button.scss */
$button-padding: 10px 20px;
$button-bg-color: blue;
$button-color: white;

.button {
  background-color: $button-bg-color;
  color: $button-color;
  padding: $button-padding;
  border: none;
  cursor: pointer;

  &__icon {
    margin-right: 5px;
  }

  &--large {
    padding: 15px 30px;
  }
}

Scaling Your BEM Implementation

As your project grows, maintaining a consistent and scalable structure becomes increasingly important. BEM can help manage this complexity by ensuring that your CSS remains modular and maintainable. Here are some strategies for scaling your BEM implementation.

Organizing Your Codebase

A well-organized codebase is crucial for scaling. Group related blocks, elements, and modifiers into directories that reflect their roles within the project. This organization helps keep your stylesheets manageable and easy to navigate.

 

 

Example Directory Structure
src/
|-- components/
|   |-- button/
|   |   |-- _large.scss
|   |   |-- _icon.scss
|   |   |-- _primary.scss
|   |   |-- button.scss
|   |-- card/
|   |   |-- _header.scss
|   |   |-- _content.scss
|   |   |-- _footer.scss
|   |   |-- card.scss
|-- layouts/
|   |-- header/
|   |   |-- _logo.scss
|   |   |-- _nav.scss
|   |   |-- header.scss
|   |-- footer/
|   |   |-- _links.scss
|   |   |-- _info.scss
|   |   |-- footer.scss

Using Variables and Mixins

Variables and mixins in preprocessors like Sass can make your CSS more efficient and maintainable. Variables allow you to define reusable values for colors, sizes, and other properties, while mixins enable you to reuse groups of styles.

Example with Sass Variables and Mixins
/* variables.scss */
$button-bg-color: blue;
$button-color: white;
$button-padding: 10px 20px;

/* mixins.scss */
@mixin button-base {
  background-color: $button-bg-color;
  color: $button-color;
  padding: $button-padding;
  border: none;
  cursor: pointer;
}

/* button.scss */
@import 'variables';
@import 'mixins';

.button {
  @include button-base;

  &__icon {
    margin-right: 5px;
  }

  &--large {
    padding: 15px 30px;
  }
}

Creating a Style Guide

A style guide is an invaluable resource for maintaining consistency across your project. It documents the design patterns, color schemes, typography, and components used throughout your site. A style guide ensures that all team members follow the same standards, making it easier to maintain a cohesive design.

Example Style Guide Sections
  • Typography: Document the font families, sizes, and weights used in your project.
  • Color Palette: Define the primary, secondary, and accent colors.
  • Components: List all reusable components, such as buttons, cards, and forms, along with their variations and modifiers.

Advanced BEM Techniques

As you become more comfortable with BEM, you can start exploring advanced techniques to further enhance your CSS workflow.

Using BEM with JavaScript

BEM can also be integrated with JavaScript to manage component states and behaviors. By using the same naming conventions in your JavaScript, you can create a seamless connection between your CSS and JavaScript, making your code more maintainable.

Example with JavaScript
<!-- HTML -->
<button class="button button--large" id="myButton">
  <span class="button__icon">⭐</span>
  Click Me
</button>
// JavaScript
document.getElementById('myButton').addEventListener('click', function() {
  this.classList.toggle('button--active');
});
/* CSS */
.button--active {
  background-color: green;
}

Nested Elements and Modifiers

In complex projects, you might encounter scenarios where elements contain other elements, or modifiers apply to multiple elements within a block. BEM’s flexibility allows you to handle these situations effectively.

Example of Nested Elements
<!-- HTML -->
<div class="card">
  <div class="card__header">
    <h2 class="card__title">Card Title</h2>
  </div>
  <div class="card__content">
    <p class="card__text">Some content here...</p>
  </div>
  <div class="card__footer">
    <button class="card__button">Action</button>
  </div>
</div>
/* CSS */
.card {
  border: 1px solid #ccc;
  padding: 20px;
}

.card__header {
  background-color: #f5f5f5;
  padding: 10px;
}

.card__title {
  font-size: 1.5em;
  margin: 0;
}

.card__content {
  padding: 10px;
}

.card__text {
  font-size: 1em;
}

.card__footer {
  background-color: #f5f5f5;
  padding: 10px;
}

.card__button {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

Challenges and Solutions with BEM

Implementing BEM can come with its own set of challenges. Understanding these challenges and knowing how to address them can help you make the most of the BEM methodology.

Managing Deeply Nested Structures

Deeply nested structures can make your BEM classes long and cumbersome. To manage this, try to keep your components as flat as possible. When deep nesting is unavoidable, consider breaking down your components into smaller, more manageable pieces.

Example of Simplifying Deep Nesting
<!-- HTML -->
<div class="menu">
  <ul class="menu__list">
    <li class="menu__item menu__item--active">
      <a href="#" class="menu__link">Home</a>
      <ul class="menu__sublist">
        <li class="menu__subitem">
          <a href="#" class="menu__sublink">Subitem 1</a>
        </li>
      </ul>
    </li>
  </ul>
</div>
/* CSS */
.menu { /* styles for menu */ }
.menu__list { /* styles for list */ }
.menu__item { /* styles for item */ }
.menu__item--active { /* styles for active item */ }
.menu__link { /* styles for link */ }
.menu__sublist { /* styles for sublist */ }
.menu__subitem { /* styles for subitem */ }
.menu__sublink { /* styles for sublink */ }

Ensuring Consistency Across Teams

Ensuring consistency across large teams can be challenging. Regular code reviews, style guide updates, and team meetings can help maintain consistency. Encourage team members to follow the BEM conventions and contribute to the style guide.

Example of Team Collaboration
  • Code Reviews: Schedule regular code reviews to ensure adherence to BEM conventions.
  • Style Guide Updates: Keep the style guide up to date with new components and best practices.
  • Team Meetings: Hold regular meetings to discuss challenges and share solutions related to BEM implementation.

Advanced BEM Concepts

BEM with CSS-in-JS

CSS-in-JS is a modern styling approach where CSS is written within JavaScript. This technique integrates well with frameworks like React, providing scoped styles and dynamic styling capabilities. Combining BEM with CSS-in-JS helps maintain the benefits of BEM’s structured methodology while leveraging the power of JavaScript.

Example with Styled Components in React

// Button.js
import styled from 'styled-components';

const Button = styled.button`
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  cursor: pointer;

  &.button__icon {
    margin-right: 5px;
  }

  &.button--large {
    padding: 15px 30px;
  }
`;

export default Button;
// App.js
import React from 'react';
import Button from './Button';

function App() {
  return (
    <Button className="button button--large">
      <span className="button__icon">⭐</span>
      Click Me
    </Button>
  );
}

export default App;

Integrating BEM with Design Systems

Design systems are comprehensive guidelines and resources for maintaining consistency in design and development across a project. Integrating BEM with a design system ensures that your components are not only reusable but also consistent with the overall design language.

Building a Component Library

Creating a component library that adheres to both BEM and your design system can streamline development and ensure design consistency. Use tools like Storybook to document and visualize your components.

Creating a component library that adheres to both BEM and your design system can streamline development and ensure design consistency. Use tools like Storybook to document and visualize your components.

Example Component Documentation with Storybook
// Button.stories.js
import React from 'react';
import Button from './Button';

export default {
  title: 'Button',
  component: Button,
};

export const Primary = () => <Button className="button">Primary</Button>;
export const Large = () => <Button className="button button--large">Large</Button>;
export const WithIcon = () => (
  <Button className="button">
    <span className="button__icon">⭐</span>
    With Icon
  </Button>
);

Using BEM with Atomic Design

Atomic Design is a methodology for creating design systems that break down components into fundamental building blocks: atoms, molecules, organisms, templates, and pages. Combining BEM with Atomic Design helps create a structured and scalable system.

Mapping BEM to Atomic Design

  • Atoms: Basic building blocks like buttons and inputs. Use BEM blocks for these components.
  • Molecules: Combinations of atoms, such as a form input with a label. Use BEM elements within these components.
  • Organisms: Complex components composed of molecules and atoms, like a header or a card. Use BEM blocks and elements.
  • Templates: Page-level components that define the layout and structure.
  • Pages: Specific instances of templates with real content.
Example of a Molecule with BEM and Atomic Design
<!-- HTML -->
<div class="form">
  <label class="form__label" for="username">Username</label>
  <input class="form__input" type="text" id="username" name="username">
</div>
/* form.scss */
.form {
  margin-bottom: 20px;
}

.form__label {
  display: block;
  margin-bottom: 5px;
}

.form__input {
  padding: 10px;
  border: 1px solid #ccc;
}

BEM for Theming and Customization

Theming allows you to apply different styles to the same components based on context, such as light and dark modes. BEM can facilitate theming by using modifiers to switch between themes.

Implementing Themes with BEM

Create base styles for your components and use modifiers to apply different themes. This approach keeps your CSS organized and makes it easy to switch themes dynamically.

Example of Light and Dark Themes
<!-- HTML -->
<button class="button button--dark">
  Dark Button
</button>
<button class="button button--light">
  Light Button
</button>
/* button.scss */
.button {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

.button--dark {
  background-color: black;
  color: white;
}

.button--light {
  background-color: white;
  color: black;
}
// Theme toggling with JavaScript
document.getElementById('themeToggle').addEventListener('click', function() {
  document.body.classList.toggle('theme--dark');
});

BEM and Performance Optimization

Performance is a critical aspect of web development, and BEM can help improve the performance of your CSS. By writing modular and reusable styles, you can reduce redundancy and improve load times.

Techniques for Optimizing CSS with BEM

  • Minimize CSS: Use tools like CSSNano to minify your CSS files, reducing their size and improving load times.
  • Combine CSS Files: Combine multiple CSS files into a single file to reduce HTTP requests.
  • Critical CSS: Extract and inline critical CSS to ensure that essential styles are loaded first, improving perceived load times.
Example of Critical CSS Extraction
<!-- HTML -->
<head>
  <style>
    .button {
      padding: 10px 20px;
      border: none;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <button class="button">Critical Button</button>
  <link rel="stylesheet" href="styles.css">
</body>

BEM for Dynamic Content

Handling Dynamic Components

Web applications often have dynamic components that change state or content based on user interactions. BEM can help manage these dynamic components by providing clear and consistent class naming conventions that make it easier to apply and manage dynamic styles.

Example of a Dynamic Component

Consider a tabbed interface where tabs can be selected, and the corresponding content is displayed.

HTML Structure
<div class="tabs">
  <div class="tabs__header">
    <button class="tabs__button tabs__button--active" data-tab="1">Tab 1</button>
    <button class="tabs__button" data-tab="2">Tab 2</button>
    <button class="tabs__button" data-tab="3">Tab 3</button>
  </div>
  <div class="tabs__content tabs__content--active" id="tab1">Content for Tab 1</div>
  <div class="tabs__content" id="tab2">Content for Tab 2</div>
  <div class="tabs__content" id="tab3">Content for Tab 3</div>
</div>
CSS
/* tabs.scss */
.tabs__button {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

.tabs__button--active {
  background-color: blue;
  color: white;
}

.tabs__content {
  display: none;
}

.tabs__content--active {
  display: block;
}
JavaScript
// tabs.js
document.querySelectorAll('.tabs__button').forEach(button => {
  button.addEventListener('click', () => {
    const tabNumber = button.dataset.tab;

    document.querySelectorAll('.tabs__button').forEach(btn => {
      btn.classList.remove('tabs__button--active');
    });
    button.classList.add('tabs__button--active');

    document.querySelectorAll('.tabs__content').forEach(content => {
      content.classList.remove('tabs__content--active');
    });
    document.getElementById(`tab${tabNumber}`).classList.add('tabs__content--active');
  });
});

BEM with State Management Libraries

When working with state management libraries like Redux or Vuex, BEM can help keep your styles organized and in sync with your application state. By combining BEM with state management, you can ensure that your components react to state changes predictably and maintainably.

Example with Redux

In a React application using Redux, you can apply BEM classes based on the application state.

React Component
// TabComponent.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { selectTab } from './tabSlice';

const TabComponent = () => {
  const activeTab = useSelector((state) => state.tabs.activeTab);
  const dispatch = useDispatch();

  const handleTabClick = (tab) => {
    dispatch(selectTab(tab));
  };

  return (
    <div className="tabs">
      <div className="tabs__header">
        {[1, 2, 3].map((tab) => (
          <button
            key={tab}
            className={`tabs__button ${activeTab === tab ? 'tabs__button--active' : ''}`}
            onClick={() => handleTabClick(tab)}
          >
            Tab {tab}
          </button>
        ))}
      </div>
      {[1, 2, 3].map((tab) => (
        <div
          key={tab}
          className={`tabs__content ${activeTab === tab ? 'tabs__content--active' : ''}`}
          id={`tab${tab}`}
        >
          Content for Tab {tab}
        </div>
      ))}
    </div>
  );
};

export default TabComponent;

Testing BEM Implementation

Unit Testing with CSS-in-JS

When using CSS-in-JS, you can leverage unit testing frameworks like Jest to ensure your styles are applied correctly. Testing your BEM classes ensures that your components behave as expected across different scenarios.

Example Unit Test with Jest

// Button.test.js
import React from 'react';
import { render } from '@testing-library/react';
import Button from './Button';

test('renders button with correct classes', () => {
  const { container } = render(<Button className="button button--large">Click Me</Button>);
  const button = container.querySelector('.button');
  expect(button).toHaveClass('button');
  expect(button).toHaveClass('button--large');
});

End-to-End Testing with Cypress

End-to-end (E2E) testing frameworks like Cypress can be used to test the functionality and appearance of your BEM-styled components in a real browser environment. This ensures that your components work as expected across different browsers and devices.

Example E2E Test with Cypress

// tabs.spec.js
describe('Tabs Component', () => {
  it('should display correct content when tab is clicked', () => {
    cy.visit('/');

    cy.get('.tabs__button[data-tab="2"]').click();
    cy.get('.tabs__content--active').should('contain.text', 'Content for Tab 2');

    cy.get('.tabs__button[data-tab="3"]').click();
    cy.get('.tabs__content--active').should('contain.text', 'Content for Tab 3');
  });
});

Integrating BEM with CSS Frameworks

Using BEM with Tailwind CSS

Tailwind CSS is a utility-first CSS framework that allows for rapid development with predefined classes. Combining BEM with Tailwind can help manage custom components while leveraging Tailwind’s utility classes for base styling.

Example of Combining BEM and Tailwind

<!-- HTML -->
<button class="button button--large bg-blue-500 text-white py-2 px-4">
  Large Button
</button>
/* button.scss */
.button {
  @apply bg-blue-500 text-white py-2 px-4; /* Tailwind utility classes */

  &--large {
    @apply py-3 px-6;
  }
}

Using BEM with Bootstrap

Bootstrap is a popular CSS framework that provides pre-styled components. You can integrate BEM with Bootstrap to customize and extend Bootstrap’s components while maintaining a clear structure.

Example of Extending Bootstrap with BEM

<!-- HTML -->
<button class="button btn btn-primary button--custom">
  Custom Button
</button>
/* button.scss */
.button {
  &--custom {
    background-color: green;
    border-color: darkgreen;
  }
}

BEM for Accessibility

Ensuring accessibility (a11y) is a crucial aspect of web development. BEM can facilitate accessibility by providing clear and consistent class names that make it easier to manage ARIA attributes and other accessibility features.

Example of Accessible Component with BEM

<!-- HTML -->
<button class="button" aria-label="Close">
  <span class="button__icon" aria-hidden="true">✖️</span>
  Close
</button>
/* button.scss */
.button {
  background-color: red;
  color: white;
  padding: 10px 20px;
  border: none;
  cursor: pointer;

  &__icon {
    margin-right: 5px;
  }
}

Internationalization (i18n) with BEM

When developing for a global audience, internationalization (i18n) is essential. BEM can help manage styles across different languages and locales by providing a consistent structure that can be easily adapted.

Example of i18n with BEM

<!-- HTML -->
<button class="button button--large">
  <span class="button__icon">⭐</span>
  {{ $t('button.clickMe') }}
</button>
// i18n.js (Vue example)
import Vue from 'vue';
import VueI18n from 'vue-i18n';

Vue.use(VueI18n);

const messages = {
  en: {
    button: {
      clickMe: 'Click Me'
    }
  },
  fr: {
    button: {
      clickMe: 'Cliquez Moi'
    }
  }
};

const i18n = new VueI18n({
  locale: 'en',
  messages
});

export default i18n;

Conclusion

Adopting the BEM methodology can revolutionize the way you write and manage CSS. By breaking down your styles into blocks, elements, and modifiers, you create a modular and maintainable codebase that is easy to understand and extend. BEM’s clear naming conventions and structured approach make it an ideal choice for teams looking to improve their CSS workflow.

The long-term benefits of using BEM are substantial. Improved readability, better reusability, avoidance of CSS conflicts, and easier collaboration are just a few advantages. As your projects grow, BEM helps maintain a clean and organized codebase, ensuring that your CSS remains efficient and scalable.

Start by implementing BEM in a small project to get a feel for its conventions and benefits. Gradually introduce it to larger projects and teams, using the strategies and techniques discussed in this article. By embracing BEM, you can write CSS that is not only efficient and reusable but also robust and easy to maintain.

Read Next: