BEM CSS: The Naming Convention That Saves Your Sanity
BEM looks ugly at first. Then you work on a large codebase without it, and suddenly those double underscores start looking beautiful.
BEM CSS: The Naming Convention That Saves Your Sanity
Let me tell you about the exact moment I started caring about CSS naming conventions.
I was working on a project that had been going for about two years. The stylesheet was 4,000+ lines. I needed to change the padding on a card component. I found .card in the CSS, changed it, and immediately broke the checkout page, the user profile page, and the blog sidebar — because there were three completely different .card styles scattered across the codebase, all using the same class name.
That's the problem BEM solves.
What BEM Actually Is
BEM stands for Block, Element, Modifier. It's a naming convention for CSS classes. That's it — no tooling, no compiler, no build step. Just a way to name things so they don't collide.
.block {}
.block__element {}
.block--modifier {}
- Block — a standalone component (
.card,.navbar,.form) - Element — a part of the block that has no meaning on its own (
.card__title,.card__image) - Modifier — a variation of the block or element (
.card--featured,.card__title--large)
Here's a real example:
<article class="card card--featured">
<img class="card__image" src="..." alt="..." />
<div class="card__body">
<h2 class="card__title">Post Title</h2>
<p class="card__excerpt">Some preview text...</p>
<a class="card__link card__link--primary" href="/post">Read more</a>
</div>
</article>
.card {
border-radius: 8px;
overflow: hidden;
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.card--featured {
border: 2px solid #f59e0b;
}
.card__image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card__title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
}
.card__link {
text-decoration: none;
color: #666;
}
.card__link--primary {
color: #2563eb;
font-weight: 500;
}
Why the Ugly Syntax Works
The first reaction most people have to BEM is "those double underscores and double dashes are ugly." Fair. But they're ugly on purpose.
The double characters (__ and --) are visually distinct. You can instantly tell which part is the block, which is the element, and which is the modifier just by scanning the class name. Compare:
/* Without BEM — what belongs to what? */
.card {
}
.card .title {
}
.card .title .icon {
}
.card.active {
}
.card .title.large {
}
/* With BEM — crystal clear */
.card {
}
.card__title {
}
.card__title-icon {
}
.card--active {
}
.card__title--large {
}
With BEM, every class is flat. No nesting. No specificity wars. Every selector has the same specificity — one class — so overriding styles is predictable. You never have to think about selector weight.
The Rules I Follow
1. Blocks are independent
A block should work anywhere you drop it. It shouldn't depend on being inside a specific parent element.
/* Bad — depends on parent */
.sidebar .card {
width: 100%;
}
/* Good — modifier on the block itself */
.card--full-width {
width: 100%;
}
2. Elements belong to blocks, not to other elements
Don't nest element selectors. BEM is flat.
/* Wrong — element of an element */
.card__body__title {
}
/* Right — element of the block */
.card__title {
}
Even if the title is inside the body in the HTML, the CSS class references the block directly. The DOM structure and the CSS naming don't have to mirror each other.
3. Modifiers don't exist alone
A modifier class is always used alongside the base class.
<!-- Wrong -->
<div class="card--featured">...</div>
<!-- Right -->
<div class="card card--featured">...</div>
The base .card provides the core styles. The modifier .card--featured only adds or overrides what's different.
4. Keep blocks small
If a block has 15+ elements, it's probably doing too much. Break it into smaller blocks.
<!-- Too many elements for one block -->
<div class="article">
<div class="article__header">
<h1 class="article__title">...</h1>
<div class="article__meta">
<span class="article__author">...</span>
<span class="article__date">...</span>
</div>
</div>
<div class="article__content">...</div>
<div class="article__footer">
<div class="article__tags">...</div>
<div class="article__share">...</div>
</div>
</div>
<!-- Better — break into smaller blocks -->
<article class="article">
<header class="article__header">
<h1 class="article__title">...</h1>
<div class="post-meta">
<span class="post-meta__author">...</span>
<span class="post-meta__date">...</span>
</div>
</header>
<div class="article__content">...</div>
<footer class="article__footer">
<div class="tag-list">...</div>
<div class="share-buttons">...</div>
</footer>
</article>
Common Mistakes
Mixing BEM with descendant selectors:
/* Defeats the purpose of BEM */
.card .card__title {
font-size: 1.5rem;
}
/* Just use the BEM class directly */
.card__title {
font-size: 1.5rem;
}
Using BEM for everything:
Utility classes and layout primitives don't need BEM. It's for components.
/* These don't need BEM */
.text-center {
text-align: center;
}
.mt-4 {
margin-top: 1rem;
}
.visually-hidden {
/* ... */
}
/* These do */
.search-form {
}
.search-form__input {
}
.search-form__button {
}
Overthinking modifier names:
/* Too vague */
.button--type-1 {
}
/* Too specific */
.button--blue-with-rounded-corners-and-shadow {
}
/* Just right */
.button--primary {
}
.button--outlined {
}
BEM in a World of Tailwind and CSS-in-JS
A reasonable question: "Why learn BEM when I use Tailwind / CSS Modules / styled-components?"
Because not every project uses those tools. Legacy codebases, email templates, CMS themes, WordPress sites, vanilla CSS projects — BEM is still the most practical convention for plain CSS. Even if you never write BEM yourself, you will encounter it in existing codebases.
And the mental model transfers. BEM teaches you to think in components, to keep styles flat and predictable, and to name things clearly. Those principles apply whether you're writing .card__title or className={styles.title}.
When NOT to Use BEM
- Utility-first projects (Tailwind CSS) — the methodology handles naming for you
- CSS Modules or CSS-in-JS — scoping is automatic, naming collisions aren't a concern
- Very small projects — a single-page site with 50 lines of CSS doesn't need BEM overhead
- Design systems with scoped components — React/Vue component scoping already isolates styles
BEM shines on medium-to-large projects where multiple people write CSS and you need a shared convention that prevents chaos. It's not the only way — but it's one of the most battle-tested ones.