Naarmate webprojecten groter en complexer worden, kan je CSS al snel veranderen in een onoverzichtelijke 'spaghetticode'. Welke CSS-regel hoort bij welk onderdeel? Waarom verandert een knop op de contactpagina plots als je de stijl van een kaartje op de homepage aanpast?
Om chaos in grote stylesheets te voorkomen, gebruiken professionele front-end developers een naamgevingsconventie. De meest gebruikte en gewaardeerde methode in het werkveld is BEM (Block Element Modifier).
In deze les leer je hoe BEM werkt, waarom het je CSS schaalbaar en onderhoudbaar maakt, en hoe je deze methodologie direct toepast in je eigen projecten.
BEM staat voor Block, Element, Modifier. Het is een doordacht systeem om klassenamen (classes) in HTML en CSS op te bouwen.
Stel dat je twee verschillende kaarten bouwt op je website en de volgende CSS schrijft:
/* Niet schaalbare CSS */
.card {
background-color: white;
padding: 20px;
}
.title {
font-size: 24px;
color: blue;
}
.button {
background-color: green;
}
Wat gaat hier mis?
.title of .button is veel te algemeen. Gebruik je elders op de pagina ook een .title, dan overlappen de stijlen elkaar..card h2 gebruikt, breek je je stijl zodra je de <h2> verandert in een <h3>.div.main .card > h2.title) om stijlen te overschrijven, wat de code onleesbaar maakt.BEM geeft elke klasse een unieke, duidelijke en voorspelbare naam. Zo zie je in HTML direct welk onderdeel waar bij hoort, en blijft je CSS plat en overzichtelijk.
BEM verdeelt een User Interface (UI) in drie duidelijke concepten:
.block__element--modifier
└─┬─┘ └──┬──┘ └───┬──┘
│ │ └─ Modifier (Status / Variant)
│ └─────────── Element (Onderdeel binnen het block)
└─────────────────── Block (Zelfstandig component)
.block)Een Block is een op zichzelf staand, herbruikbaar component van de pagina. Het heeft betekenis op zichzelf en kan overal op de website geplaatst worden.
.card, .navbar, .footer, .button, .search-form.product-card).<article class="card">
<!-- Inhoud van het block -->
</article>
.block__element)Een Element is een specifiek onderdeel binnen een Block. Het heeft buiten het Block geen eigen betekenis of bestaansrecht.
__)..card__title, .card__image, .card__body, .navbar__link<article class="card">
<img src="foto.jpg" class="card__image" alt="Afbeelding">
<h2 class="card__title">Kaart Titel</h2>
<p class="card__text">Dit is de beschrijving van de kaart.</p>
</article>
Belangrijk: Een Element bevindt zich altijd binnen een Block, maar de naamgeving weerspiegelt alleen het hoofdband van het Block. Schrijf dus niet.card__body__title, maar gewoon.card__title!
.block--modifier of .block__element--modifier)Een Modifier geeft een variant, status of alternatieve vorm aan van een Block of een Element.
--)..card--featured, .button--large, .button--secondary.card__title--highlighted, .navbar__link--active<!-- Een standaard knop -->
<button class="button">Klik hier</button>
<!-- Een grote, primaire knop (Block + Modifier) -->
<button class="button button--primary button--large">Klik hier</button>
Tip: Een Modifier wordt samen met de basisklasse gebruikt, nooit alleen! Je schrijft dusclass="button button--primary", niet alleenclass="button--primary".
Laten me een realistisch onderdeel bouwen met BEM: een Product Card met een normale variant en een uitverkochte (sold-out) variant.
<!-- Standaard Product Card -->
<article class="product-card">
<img src="product.jpg" alt="Schoenen" class="product-card__image">
<div class="product-card__content">
<h3 class="product-card__title">Hardloopschoenen Pro</h3>
<p class="product-card__price">€ 119,99</p>
<button class="product-card__button">In Winkelmand</button>
</div>
</article>
<!-- Uitverkochte Product Card (met Block Modifier) -->
<article class="product-card product-card--sold-out">
<img src="product.jpg" alt="Schoenen" class="product-card__image">
<div class="product-card__content">
<h3 class="product-card__title">Hardloopschoenen Pro</h3>
<p class="product-card__price product-card__price--discount">€ 89,99</p>
<button class="product-card__button product-card__button--disabled" disabled>
Uitverkocht
</button>
</div>
</article>
/* ====================================================== */
/* 1. BLOCK */
/* ====================================================== */
.product-card {
background-color: #ffffff;
border-radius: 12px;
border: 1px solid #e2e8f0;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease;
}
/* ====================================================== */
/* 2. ELEMENTS */
/* ====================================================== */
.product-card__image {
width: 100%;
height: 200px;
object-fit: cover;
}
.product-card__content {
padding: 20px;
}
.product-card__title {
font-size: 1.25rem;
color: #0f172a;
margin-bottom: 8px;
}
.product-card__price {
font-size: 1.1rem;
font-weight: bold;
color: #2563eb;
margin-bottom: 16px;
}
.product-card__button {
width: 100%;
padding: 10px;
background-color: #2563eb;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
}
/* ====================================================== */
/* 3. MODIFIERS */
/* ====================================================== */
/* Block Modifier: Uitverkocht kaartje vervagen */
.product-card--sold-out {
opacity: 0.7;
border-color: #cbd5e1;
}
/* Element Modifier: Kortingsprijs extra accentueren */
.product-card__price--discount {
color: #dc2626; /* Rood */
}
/* Element Modifier: Uitgeschakelde knop */
.product-card__button--disabled {
background-color: #94a3b8;
cursor: not-allowed;
}
Om het maximale uit BEM te halen, houd je je aan deze vier cruciale regels:
Gebruik in BEM geen HTML-elementen (h2, div, p) of ID's (#header) in je CSS-selectoren. Zo blijft de specificity (prioriteit van CSS) van al je regels exact gelijk ($0,1,0$).
/* ❌ FOUT: Sterk afhankelijk van HTML-tags */
.card h2 {
font-size: 20px;
}
/* 100% goed: Onafhankelijk en flexibel */
.card__title {
font-size: 20px;
}
Een veelgemaakte fout is het kopiëren van de HTML-diepte in de klassenaam.
<!-- ❌ FOUT: Dubbele underscores opeenstapelen -->
<div class="card">
<div class="card__header">
<h2 class="card__header__title">Foute titel</h2>
</div>
</div>
<!-- 100% goed: Houd de BEM-naam plat, ongeacht de HTML-diepte -->
<div class="card">
<div class="card__header">
<h2 class="card__title">Goede titel</h2>
</div>
</div>
Als een Block of Element uit meerdere woorden bestaat, gebruik je een enkel koppelteken (-) om de woorden te scheiden. Je reserveert __ en -- uitsluitend voor de BEM-structuur.
.shopping-cart $\rightarrow$ Block.shopping-cart__item-title $\rightarrow$ Element van shopping-cart.shopping-cart--dark-mode $\rightarrow$ Modifier van shopping-cartSoms bent je geneigd om in CSS selectoren achter elkaar te plakken:
/* ❌ ONNODIG: Verhoogt de specificity zonder reden */
.product-card .product-card__title {
color: black;
}
/* 100% goed: Omdat .product-card__title al uniek is! */
.product-card__title {
color: black;
}
In professionele ontwikkelteams heeft BEM gigantische voordelen:
.button) gemakkelijk naar een ander project kopiëren zonder dat de stijl breekt.Hieronder zie je een volledige BEM-implementatie voor een responsieve header met navigatie.
index.html)<header class="main-nav main-nav--sticky">
<div class="main-nav__brand">
<a href="#" class="main-nav__logo">Studio<span>Viso</span></a>
</div>
<nav class="main-nav__menu">
<a href="#" class="main-nav__link main-nav__link--active">Home</a>
<a href="#" class="main-nav__link">Diensten</a>
<a href="#" class="main-nav__link">Portfolio</a>
<a href="#" class="main-nav__link main-nav__link--cta">Contact</a>
</nav>
</header>
style.css)/* Block */
.main-nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 32px;
background-color: #0f172a;
color: #ffffff;
}
/* Block Modifier */
.main-nav--sticky {
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* Elements */
.main-nav__logo {
font-size: 1.5rem;
font-weight: bold;
color: #ffffff;
text-decoration: none;
}
.main-nav__logo span {
color: #38bdf8;
}
.main-nav__menu {
display: flex;
gap: 20px;
align-items: center;
}
.main-nav__link {
color: #94a3b8;
text-decoration: none;
font-weight: 500;
transition: color 0.2s ease;
}
.main-nav__link:hover {
color: #ffffff;
}
/* Element Modifiers */
.main-nav__link--active {
color: #38bdf8;
border-bottom: 2px solid #38bdf8;
}
.main-nav__link--cta {
background-color: #38bdf8;
color: #0f172a;
padding: 8px 16px;
border-radius: 6px;
font-weight: bold;
}
.main-nav__link--cta:hover {
background-color: #0284c7;
color: #ffffff;
}
.block): Een zelfstandig, herbruikbaar component (bijv. .card)..block__element): Een vast onderdeel van het block, verbonden met a dubbele underscore __ (bijv. .card__title)..block--modifier): Een status of variant van een block of element, verbonden met a dubbel koppelteken -- (bijv. .card--featured).-) voor woordencombinaties (bijv. .product-card)..card__title, nooit .card__header__title).