Block Element Modifier (BEM)
BEM is a method for naming CSS classes. It helps in structuring and building front end components and layouts. For an introduction to the topic, please read the official BEM website first.
We are currently using a modified form of SMACSS for the CSS architecture. For more information, please see the official SCMCSS website.
BEM - a small explanation
In this Style Guide we try to build class-based selectors whenever possible. Whenever possible, tag selectors should only be addressed with :is()
or better with :where()
pseudo selectors.
To name the classes we use BEM. In the following chapter we explain how the classes are named and what to look out for.
Block
A component or layout gets a block name, e.g. .badge
or for multiple words .badge-bar
.
Element
If you have an element which is embedded within a component and logically belongs to it, you combine the block name with the element name separated by two underscores, e.g.:
- In the component
.button
that contains a label, the element name becomes.button__label
- In the component
.page-header
in which there is a navigation with a view actions, this gets the element name.page-header__navi-action
.
When an element is within an element, you don’t start combining those element names together. Just follow the rule above: BLOCK__ELEMENT
<!-- WRONG WRONG -->
<div class="listing">
<div class="listing__list">
<div class="listing__list__item"><span class="listing__list__item__text">WRONG</span></div>
<div class="listing__list__item"><span class="listing__list__item__text">WRONG</span></div>
</div>
</div>
<!-- CORRECT CORRECT -->
<div class="listing">
<div class="listing__list">
<div class="listing__item"><span class="listing__text">CORRECT</span></div>
<div class="listing__item"><span class="listing__text">">CORRECT</span></div>
</div>
</div>
Modifier
A part of a component always needs a default state. It does not matter whether it is a block or an element. However, parts of a component or even the entire component often change at certain events (hover, click, DOMready, etc.). These changes are represented by modifiers. These modifiers change the default state of the block or element.
Modifier on Block
For example, we have a component .snapshot
that displays other buttons when the user is logged in. So there is a default view (the user is not logged in) and a special view that changes or “modifies” the default behavior. For modifiers that apply to the whole component, combine the block name with the modifier name separated by two dashes, e.g.:
- The color change of the whole
.badge
is described by the modifier.badge--danger
. - The
.page-header
doesn’t show the menu when you scroll down, unless you click the hamburger item. This will add the.page-header--show-menu
block modifier, which will show the menu.
Modifier on Element
Often only a single element within a component changes in a certain way and you have to determine exactly which element. You can’t apply the modifier to the component, you have to add the modifier specifically to the element. For modifiers applied to an element, combine the name of the whole element with the modifier name separated by two hyphens, e.g.:
- In the
.listing
component there are many.listing__item
elements. A single one of these items can be marked, giving it a different background color than the default. To do this, use the element modifier.listing__item--active
. - In our
<main>
layout we use the.page-content
block. In it we have several<section>
which we give the.page-content__container
element class. Normally all<article>
are in the section one below the other. But sometimes we want to have an<aside>
next to the articles in a second col. So that the CSS knows that these should be next to each other, we have to give the container element the info to change its default behavior. We do this by customizing the element using the element modifier.page-content__container--2-cols
.
Below is an HTML example with a component modifier and an element modifier:
<div class="listing listing--stretch">
<ul class="listing__list">
<li class="listing__item">
<span class="listing__text">I am in a default state</span>
</li>
<li class="listing__item listing__item--active">
<span class="listing__text">I am modified</span>
</li>
</ul>
</div>
Global Modifier
We make exceptions for global modifiers, such as .display-none
. We call this a “(global) modifier”, but there is only one default state for the class. There is no modified version of it. A modified version could be .display-none--or-maybe-you-d-rather-be-a-block
, but that doesn’t make any sense o.O'
.display {
&-none {
display: none; // correct
&--or-maybe-you-d-rather-be-a-block {
display: block; // really strange O.o'
}
}
&-block {
display: block; // better
}
}
How to work with BEM and SCSS
BEM and SCSS work excellently together through nesting. If done intelligently, the resulting selectors will be as short as possible.
A very good example is the listing component. An excerpt from the _listing.scss
is shown below:
/* THE SOURCE _listing.scss */
.listing { /* the block */
display: inline-block;
width: max-content;
min-width: $-min-width;
max-height: var(--sg-listing-max-height, $-max-height);
&__list { /* an element */
display: block;
width: 100%;
&:is(ol) { /* do not use "ol.listing__list", use the :is() or :where() selector instead */
counter-reset: listing-list;
}
}
&__item { /* an element */
display: block;
width: 100%;
transition: var(--sg-listing-item-transition, $-item-transition);
&:not(:first-child) .listing__text {
transition: var(--sg-listing-item-transition, $-item-transition);
border-top: var(--sg-listing-item-separator-default, $-item-separator-default);
}
&--active { /* an element modifier */
background: var(--sg-listing-item-background-hover, $-item-background-hover);
&:not(:first-child) .listing__text {
border-top: var(--sg-listing-item-separator-hover, $-item-separator-hover);
}
+ .listing__item .listing__text {
border-top: var(--sg-listing-item-separator-hover, $-item-separator-hover);
}
}
}
}
&__text { /* an element */
display: block;
margin: var(--sg-listing-text-margin, $-text-margin);
padding: var(--sg-listing-text-padding, $-text-padding);
}
&--stretch { /* a block modifier */
width: 100%;
}
&--suppress-vertical-scrolling { /* a block modifier */
max-height: none;
}
}
/* THE BUILDED style.css */
.listing { /* specificity: (0, 1, 0) */
display: inline-block;
width: max-content;
min-width: 18em;
max-height: var(--sg-listing-max-height, 30vh);
}
.listing__list { /* specificity: (0, 1, 0) */
display: block;
width: 100%;
}
.listing__list:is(ol) { /* specificity: (0, 2, 1) */
counter-reset: listing-list;
}
.listing__item { /* specificity: (0, 1, 0) */
display: block;
width: 100%;
transition: var(--sg-listing-item-transition, all 50ms ease-in-out);
}
.listing__item:not(:first-child) .listing__text { /* specificity: (0, 3, 0) */
transition: var(--sg-listing-item-transition, all 50ms ease-in-out);
border-top: var(--sg-listing-item-separator-default, 1px solid rgb(223, 227, 231));
}
.listing__item--active { /* specificity: (0, 1, 0) */
background: var(--sg-listing-item-background-hover, rgb(223, 227, 231));
}
.listing__item--active:not(:first-child) .listing__text { /* specificity: (0, 3, 0) */
border-top: var(--sg-listing-item-separator-hover, 1px solid transparent);
}
.listing__item + .listing__item .listing__text { /* specificity: (0, 3, 0) */
border-top: var(--sg-listing-item-separator-hover, 1px solid );
}
.listing__text { /* specificity: (0, 1, 0) */
display: block;
margin: var(--sg-listing-text-margin, 0);
padding: var(--sg-listing-text-padding, 0.5em 0.75em);
}
.listing--stretch { /* specificity: (0, 1, 0) */
width: 100%;
}
.listing--suppress-vertical-scrolling { /* specificity: (0, 1, 0) */
max-height: none;
}
In the examples above, the CSS Specificity never exceeds 30, despite the sometimes complex selectors.