BEM Methodology with SASS
BEM Methodology with SASS
BEM (Block Element Modifier) is a powerful naming convention for CSS classes that promotes reusability, maintainability, and scalability. When combined with SASS, BEM becomes even more elegant and efficient. In this comprehensive lesson, we'll explore how to implement BEM methodology effectively using SASS's nesting capabilities.
What is BEM?
BEM stands for Block Element Modifier. It's a naming methodology that helps you create reusable components and makes your code easier to understand and maintain.
The Three Core Concepts
BEM Structure
// Block: Standalone component
.block { }
// Element: Part of a block (uses double underscore __)
.block__element { }
// Modifier: Different state or variation (uses double dash --)
.block--modifier { }
.block__element--modifier { }
1. Block
A block is an independent, reusable component. It represents a meaningful standalone entity.
- Examples:
header,menu,button,card,search-form - Blocks can contain other blocks
- Blocks are self-contained and don't depend on other page elements
Block Example
<!-- Block: card -->
<div class="card">
<!-- Card content -->
</div>
<!-- Block: button -->
<button class="button">Click me</button>
2. Element
An element is a part of a block that has no standalone meaning. Elements are semantically tied to their block.
- Elements are represented with double underscore:
block__element - Examples:
card__title,card__image,menu__item - Elements exist within the context of their block
- Elements cannot exist outside their block
Element Example
<div class="card">
<img class="card__image" src="photo.jpg" alt="Photo">
<h3 class="card__title">Card Title</h3>
<p class="card__description">Card description text.</p>
<button class="card__button">Read More</button>
</div>
block__element__subelement. Instead, think about whether the subelement should be a separate element or even a new block.
3. Modifier
A modifier represents a different state or variation of a block or element.
- Modifiers are represented with double dash:
block--modifier - Examples:
button--primary,card--featured,menu__item--active - Modifiers change appearance, behavior, or state
- Use modifiers for themes, sizes, states, and variations
Modifier Example
<!-- Normal button -->
<button class="button">Normal Button</button>
<!-- Primary button modifier -->
<button class="button button--primary">Primary Button</button>
<!-- Large button modifier -->
<button class="button button--large">Large Button</button>
<!-- Combined modifiers -->
<button class="button button--primary button--large">Large Primary</button>
Why BEM Exists
Problem: CSS Specificity Wars
Without a naming convention, CSS can become a maintenance nightmare:
Problematic CSS Without BEM
/* Too generic, will conflict */
.title { }
.image { }
.button { }
/* Too specific, hard to override */
.sidebar .widget .title { }
#header nav ul li a { }
/* Ambiguous naming */
.primary { }
.red { }
.big { }
BEM Solutions
- Low Specificity: BEM uses only class selectors (specificity: 0,0,1,0)
- No Conflicts: Unique block names prevent style collisions
- Readability: Class names describe structure and purpose
- Predictability: You know exactly where styles will apply
- Scalability: Easy to add new components without breaking existing ones
BEM Solution
/* Clear, specific, low specificity */
.card { }
.card__title { }
.card__image { }
.card--featured { }
.article { }
.article__title { } /* Won't conflict with .card__title */
.article__image { }
SASS & BEM: Perfect Together
SASS's parent selector & makes writing BEM incredibly clean and maintainable:
BEM with SASS Nesting
// Without SASS - repetitive
.card { }
.card__header { }
.card__body { }
.card__footer { }
.card--featured { }
// With SASS - DRY and organized
.card {
// Block styles
background: white;
border: 1px solid #ddd;
border-radius: 4px;
// Elements (use & to reference parent)
&__header {
padding: 1rem;
border-bottom: 1px solid #ddd;
}
&__body {
padding: 1rem;
}
&__footer {
padding: 1rem;
border-top: 1px solid #ddd;
}
// Modifiers
&--featured {
border: 2px solid #007bff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
&--dark {
background: #333;
color: white;
}
}
& symbol in SASS gets replaced with the parent selector. So &__header inside .card compiles to .card__header.
Real-World BEM Examples
Example 1: Navigation Component
Navigation HTML
<nav class="nav">
<ul class="nav__list">
<li class="nav__item">
<a href="#" class="nav__link nav__link--active">Home</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">About</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Services</a>
</li>
<li class="nav__item nav__item--dropdown">
<a href="#" class="nav__link">Products</a>
<ul class="nav__dropdown">
<li class="nav__dropdown-item">
<a href="#" class="nav__dropdown-link">Product 1</a>
</li>
</ul>
</li>
</ul>
</nav>
Navigation SCSS with BEM
.nav {
background: #333;
padding: 0 1rem;
&__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
}
&__item {
position: relative;
&--dropdown {
&:hover .nav__dropdown {
display: block;
}
}
}
&__link {
display: block;
padding: 1rem;
color: white;
text-decoration: none;
transition: background 0.3s;
&:hover {
background: rgba(255, 255, 255, 0.1);
}
&--active {
background: #007bff;
font-weight: bold;
}
}
&__dropdown {
display: none;
position: absolute;
top: 100%;
left: 0;
background: #444;
min-width: 200px;
list-style: none;
margin: 0;
padding: 0;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
&__dropdown-item {
border-bottom: 1px solid #555;
&:last-child {
border-bottom: none;
}
}
&__dropdown-link {
display: block;
padding: 0.75rem 1rem;
color: white;
text-decoration: none;
&:hover {
background: #555;
}
}
}
Example 2: Card Component
Card HTML with BEM
<article class="card card--featured">
<img class="card__image" src="photo.jpg" alt="Photo">
<div class="card__content">
<span class="card__badge card__badge--new">New</span>
<h2 class="card__title">Article Title</h2>
<p class="card__description">
Article description goes here with some preview text.
</p>
<div class="card__meta">
<span class="card__author">John Doe</span>
<span class="card__date">Jan 15, 2024</span>
</div>
</div>
<footer class="card__footer">
<button class="card__button card__button--primary">Read More</button>
<button class="card__button card__button--outline">Save</button>
</footer>
</article>
Card SCSS with BEM
.card {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
&__image {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}
&__content {
padding: 1.5rem;
position: relative;
}
&__badge {
position: absolute;
top: 1rem;
right: 1rem;
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: bold;
text-transform: uppercase;
&--new {
background: #28a745;
color: white;
}
&--hot {
background: #dc3545;
color: white;
}
}
&__title {
margin: 0 0 0.5rem;
font-size: 1.5rem;
color: #333;
}
&__description {
margin: 0 0 1rem;
color: #666;
line-height: 1.6;
}
&__meta {
display: flex;
gap: 1rem;
font-size: 0.875rem;
color: #999;
}
&__author {
font-weight: 500;
}
&__date {
&::before {
content: "•";
margin-right: 0.5rem;
}
}
&__footer {
padding: 1rem 1.5rem;
background: #f8f9fa;
display: flex;
gap: 0.5rem;
}
&__button {
flex: 1;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
&--primary {
background: #007bff;
color: white;
&:hover {
background: #0056b3;
}
}
&--outline {
background: transparent;
color: #007bff;
border: 1px solid #007bff;
&:hover {
background: #007bff;
color: white;
}
}
}
// Card modifier: featured
&--featured {
border: 2px solid #ffc107;
.card__title {
color: #ffc107;
}
}
// Card modifier: dark theme
&--dark {
background: #333;
color: white;
.card__title {
color: white;
}
.card__description {
color: #ccc;
}
.card__footer {
background: #222;
}
}
}
Example 3: Form Component
Form SCSS with BEM
.form {
max-width: 600px;
margin: 0 auto;
padding: 2rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
&__group {
margin-bottom: 1.5rem;
&--inline {
display: flex;
gap: 1rem;
.form__field {
flex: 1;
}
}
}
&__label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #333;
&--required::after {
content: "*";
color: #dc3545;
margin-left: 0.25rem;
}
}
&__field {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.3s, box-shadow 0.3s;
&:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}
&--error {
border-color: #dc3545;
&:focus {
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1);
}
}
&--success {
border-color: #28a745;
&:focus {
box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.1);
}
}
}
&__error {
display: block;
margin-top: 0.25rem;
font-size: 0.875rem;
color: #dc3545;
}
&__hint {
display: block;
margin-top: 0.25rem;
font-size: 0.875rem;
color: #6c757d;
}
&__checkbox {
display: flex;
align-items: center;
gap: 0.5rem;
input {
width: auto;
}
label {
margin: 0;
}
}
&__submit {
width: 100%;
padding: 0.75rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background 0.3s;
&:hover {
background: #0056b3;
}
&:disabled {
background: #6c757d;
cursor: not-allowed;
}
}
// Form size modifiers
&--small {
.form__field,
.form__submit {
padding: 0.5rem;
font-size: 0.875rem;
}
}
&--large {
.form__field,
.form__submit {
padding: 1rem;
font-size: 1.125rem;
}
}
}
BEM Pitfalls and Common Mistakes
Mistake 1: Over-nesting Elements
Wrong: Nested Elements
// DON'T DO THIS
.card__header__title { }
.card__body__content__text { }
// This defeats BEM's purpose and creates long class names
Correct: Flat Structure
// DO THIS INSTEAD
.card__header { }
.card__title { } /* Even if it's inside header */
.card__text { } /* Even if it's deeply nested */
// Elements are always direct children of the block (in naming)
Mistake 2: Using Element Selectors
Wrong: Coupling to HTML Tags
// DON'T DO THIS
.card h2 { }
.card div.content { }
.card > p { }
// This breaks BEM and adds unnecessary specificity
Correct: Class-Only Selectors
// DO THIS
.card__title { }
.card__content { }
.card__text { }
// Always use classes, never rely on HTML structure
Mistake 3: Modifier Without Base Class
Wrong: Modifier Only
<!-- DON'T DO THIS -->
<button class="button--primary">Click me</button>
/* Missing base styles */
Correct: Base + Modifier
<!-- DO THIS -->
<button class="button button--primary">Click me</button>
/* Both classes are needed: base styles + modifier styles */
Mistake 4: Deeply Nested SASS
Wrong: Too Much Nesting
// DON'T DO THIS - Hard to read and override
.card {
&__header {
&__title {
&--large {
// 4 levels deep!
}
}
}
}
Correct: Shallow Nesting
// DO THIS - Keep nesting shallow
.card {
&__header { }
&__title { }
&__title--large { }
}
// Maximum 2-3 levels of nesting in SASS
BEM Variations
Two Dashes vs Two Underscores (Original BEM)
The original BEM uses different separators:
Original BEM Syntax
/* Block */
.block { }
/* Element (single underscore) */
.block_element { }
/* Modifier (single underscore) */
.block_modifier { }
.block_element_modifier { }
/* Less common today, but still valid */
ABEM (Atomic BEM)
ABEM adds atomic utility classes to BEM:
ABEM Example
<div class="card -mt-3 -p-2">
<!-- BEM for components, atomic utilities for spacing -->
</div>
/* Atomic utilities use dash prefix */
.-mt-3 { margin-top: 1rem; }
.-p-2 { padding: 0.5rem; }
BEMIT (BEM + ITCSS + Namespaces)
BEMIT adds namespaced prefixes to BEM classes:
BEMIT Example
/* Component namespace */
.c-card { }
.c-card__title { }
/* Object namespace */
.o-layout { }
.o-layout__item { }
/* Utility namespace */
.u-text-center { }
/* Hack namespace */
.h-clearfix { }
/* Theme namespace */
.t-dark { }
/* State namespace */
.is-active { }
.has-dropdown { }
Exercise 1: Build a BEM Navigation
Create a responsive navigation menu using BEM:
- Block:
.nav - Elements:
.nav__list,.nav__item,.nav__link - Modifiers:
.nav__link--active,.nav--mobile - Use SASS nesting with the
&symbol - Include hover states and transitions
- Mobile modifier that stacks items vertically
Exercise 2: Create a BEM Modal Component
Build a modal dialog using BEM methodology:
- Block:
.modal - Elements:
.modal__overlay,.modal__dialog,.modal__header,.modal__body,.modal__footer,.modal__close - Modifiers:
.modal--small,.modal--large,.modal--fullscreen - Include animations for opening/closing
- Write the complete SCSS with proper BEM nesting
Exercise 3: Refactor Non-BEM Code
Take this non-BEM code and refactor it to use BEM properly:
<div class="product-card featured">
<img class="image" src="product.jpg">
<h3 class="title primary">Product Name</h3>
<p class="description">Product details</p>
<div class="price">
<span class="amount">$99.99</span>
<span class="old-price">$149.99</span>
</div>
<button class="button add-to-cart">Add to Cart</button>
</div>
Refactor to use proper BEM naming with blocks, elements, and modifiers.
Summary
In this lesson, you've mastered BEM methodology with SASS. You now understand:
- The three core concepts: Block, Element, and Modifier
- Why BEM exists and the problems it solves (specificity, conflicts, maintainability)
- How to use SASS's
&parent selector to write clean BEM code - Real-world examples: navigation, cards, forms, and modals
- Common BEM pitfalls and how to avoid them
- BEM variations: ABEM, BEMIT, and namespaced approaches
- Best practices for scalable, maintainable CSS architecture
BEM combined with SASS provides a powerful, scalable approach to writing CSS. It makes your code more predictable, easier to understand, and significantly reduces maintenance headaches in large projects.