Website Markup Using BEM Methodology
BEM (Block, Element, Modifier) is naming methodology and component organization developed at Yandex. In large projects BEM solves concrete problem: CSS is global, and without naming system styles from one part break another.
Syntax
.block {} /* Block — independent component */
.block__element {} /* Element — part of block, doesn't exist separately */
.block--modifier {} /* Modifier — state or variation of block */
.block__element--mod {} /* Element modifier */
Double underscore for element. Double dash for modifier. No three levels of nesting in class (block__element__subelement — error).
Practical Example: Product Card
<article class="product-card product-card--featured">
<div class="product-card__badge product-card__badge--new">New</div>
<figure class="product-card__media">
<img
class="product-card__image"
src="product.webp"
alt="Product name"
width="320"
height="240"
>
</figure>
<div class="product-card__body">
<h2 class="product-card__title">Product name</h2>
<p class="product-card__description">Brief description...</p>
<div class="product-card__pricing">
<span class="product-card__price product-card__price--current">$29.90</span>
<span class="product-card__price product-card__price--old">$44.90</span>
<span class="product-card__discount">−33%</span>
</div>
</div>
<footer class="product-card__footer">
<button class="btn btn--primary btn--full-width product-card__action">
Add to cart
</button>
<button class="wishlist-btn product-card__wishlist" aria-label="Add to wishlist">
<svg class="wishlist-btn__icon">...</svg>
</button>
</footer>
</article>
CSS:
/* Block */
.product-card {
display: grid;
grid-template-rows: auto 1fr auto;
border: 1px solid var(--color-border);
border-radius: 8px;
overflow: hidden;
background: var(--color-surface);
transition: box-shadow 200ms ease;
}
.product-card:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
/* Block modifier */
.product-card--featured {
border-color: var(--color-accent);
}
/* Elements */
.product-card__media {
position: relative;
aspect-ratio: 4 / 3;
overflow: hidden;
margin: 0;
}
.product-card__image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 300ms ease;
}
.product-card:hover .product-card__image {
transform: scale(1.05);
}
.product-card__badge {
position: absolute;
top: 12px;
left: 12px;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
}
.product-card__badge--new { background: #22c55e; color: white; }
.product-card__badge--sale { background: #ef4444; color: white; }
.product-card__body {
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.product-card__pricing {
display: flex;
align-items: baseline;
gap: 8px;
}
.product-card__price--current {
font-size: 1.25rem;
font-weight: 700;
color: var(--color-text);
}
.product-card__price--old {
font-size: 0.875rem;
color: var(--color-muted);
text-decoration: line-through;
}
.product-card__footer {
padding: 12px 16px;
display: flex;
gap: 8px;
border-top: 1px solid var(--color-border);
}
.product-card__action { flex: 1; }
File Structure (File-per-block)
src/
blocks/
product-card/
product-card.html # Template / Storybook
product-card.css # Block styles
product-card.js # JS (optional)
btn/
btn.css
wishlist-btn/
wishlist-btn.css
header/
header.css
header.js
One file per block — clear separation. In build (Vite, Webpack) import only needed blocks.
Differences from CSS Modules and Utility-First
| Approach | Strengths | Weaknesses |
|---|---|---|
| BEM | Readable classes, explicit structure, no tooling required | Verbose, risk of "what is this element anyway" |
| CSS Modules | Automatic isolation, no name conflicts | Requires bundler, harder debug in DevTools |
| Tailwind | Prototyping speed, no naming | Long className, hard to read HTML |
BEM remains valid choice for:
- Teams without single JS framework (MPA, CMS templates)
- Projects where designers/developers work with plain HTML/CSS
- Style guides and UI libraries used in different contexts
Common Mistakes
Three levels of nesting in class:
<!-- Wrong -->
<div class="nav__list__item">
<!-- Right — item is element of nav block, not list -->
<div class="nav__item">
Modifier without base class:
<!-- Wrong -->
<button class="btn--primary">
<!-- Right -->
<button class="btn btn--primary">
Contextual styles via nesting:
/* Wrong — context binding, breaks isolation */
.sidebar .product-card { font-size: 0.875rem; }
/* Right — modifier */
.product-card--compact { font-size: 0.875rem; }
Global state styles instead of modifiers:
/* Wrong */
.is-active { color: red; }
/* Right -->
.nav__link--active { color: var(--color-accent); }
Timeframe
BEM markup takes slightly more time due to thoughtful naming, but saves time on maintenance:
| Scope | Time |
|---|---|
| Landing page (6–8 blocks) | 1.5–2.5 days |
| Corporate site | 4–6 days |
| UI kit (20–30 components) | 5–8 days |







