Table of contents

Introduction

In my previous post I talked about short term goals for this site. One goal was to "better utilize class selectors for cleaner CSS." I had a few ideas of how to do this myself, but also came across the idea of BEM which stands for Block Element Modifier.

I was a bit skeptical at first, but decided to give BEM a try. I am now half way through reorganizing my site using this methodology and look forwards to finishing the job. BEM is by no means perfect, but it does a wonderful job of organizing my code and even has the unexpected advantage of reducing specificity.

Specificity

So what is specificity and why is it important? According to the MDN Web Docs

Specificity is the algorithm used by browsers to determine the CSS declaration that is the most relevant to an element, which in turn, determines the property value to apply to the element.

Their documentation goes into much greater detail, but the basic idea is that each CSS selector has a specific weight. Simplifying things a bit, we have three basic selectors, the type selector, class selector and ID selector. The type selector which includes tags such as <a>, <h1> and <div> has the lowest weight of the basic selectors while the ID selector has the highest weight.

A selector can consist of multiple basic selectors. For example here is a selector containing all three basic selectors.

#layout .card .text em {
    \* styles *\
}

Using the convention (ID-weight, class-weight, type-weight) we get a weight of (1, 2, 1). Which selectors we have is significant. An ID selector always has a higher weight than a class selector which in turn always has a higher weight than a type selector. Consider the following example in which both selectors target the same element.

.widget div a {  \* specificity (0, 1, 2) *\
    color: red;
}
.widget a { \* specificity (0, 1, 1) *\
    color: blue;
}

The selector with color: red has a higher specificity. Since we have a tie for number of ID and class selectors we have to look at the number of of type selectors for the tie-breaker. Since the first selector has one more type selector than than the second selector the hyperlink text will be red even though the second selector shows up later in the cascade. Consider a change to the second selector.

.widget div a {  \* specificity (0, 1, 2) *\
    color: red;
}
.widget a.link { \* specificity (0, 2, 1) *\
    color: blue;
}

The second selector now has a higher specificity due to the additional class selector. Thus, regardless of cascade order, the hyperlink would be blue. That is to say, specificity overrides the cascade.

Putting everything together, when two or more selectors target the same element we determine which selector's declarations get applied by prioritizing a higher specificity and for a tie we can consider the cascade order. This makes it important to try to keep specificity predictable to avoid unexpected style results.

BEM

Due to the unexpected consequences caused by high specificity many programmers are encouraged to keep specificity as low as possible. One powerful structural tool to accomplish this is BEM.

So how does BEM keep specificity low and provide additional benefits such as code organization?

BEM stands for Block__Element--Modifier. Our goal is to break our HTML into components referred to as blocks. Each block can contain multiple elements. For example a block could be a div tag containing a couple elements. The HTML could look like the following:

<div class="block">
    <h1 class="block__title">
        How to Create a Block
    </h1>
    <p class="block__text">
        Using BEM we create a block as follows...
    </p>
</div>

For our stylesheet we would have something like this:

.block { }
.block__title { }
.block__text { }

Notice that each selector has a weight of (0, 1, 0). So each selector has the same specificity. Now what about the modifier? Suppose in our example we are reusing our block code. We may like to make some slight changes to its presentation. We do this using a modifier. Suppose we want to modify the title. Instead of updating the .block__title declaration block we add a modifier to it instead. In our CSS we add

.block__title--colorful {
    color: green;
}

And we modifier our HTML to have the new class

<div class="block">
    <h1 class="block__title block__title--colorful">
        How to Create a Block
    </h1>
    <p class="block__text">
        Using BEM we create a block as follows...
    </p>
</div>

Notice that even our modifier has a specificity of (0, 1, 0). Creating consistent specificity levels is one of the key features of BEM. By following this methodology we can help prevent unexpected specificity issues. This method also enforces a degree of organization in the stylesheet. For example instead of using BEM to style our blocks we could have something like

div.block { }
div h1.block__title { }
div p { }

This would target the same elements as before, but in the last selector we could target all <div> tags containing a <p> tag. This could have unplanned for affects to other selectors. Another issue is that our specificity is all over the place. Suppose we wanted to modify our title. our modifier

.block__title--colorful {
    color: green;
}

Would no longer work if we had already set a color attribute. This is due to having a lower specificity and targeting the same element.

Of course BEM isn't perfect. Class names can get large and an HTML tag with multiple modifiers could end up looking bloated. Still this is a pretty good way to make organized well contained code.

The :where() Pseudo Class Function

One issue I have with BEM is that it doesn't support applying localized base styles. Strict adherence to BEM methodology would require all CSS to to have a single selector. Suppose I have a core CSS file along with app specific CSS files. The core styles may have a selector such as

a {
    color: lawngreen;
}
a:hover {
    color: white;
}

This may not be BEM approved, but if you want defaults that are not set by the browser you may need to target type selectors occasionally. Suppose the above styles apply to the header, footer and sidebar of the site. The colors should appear consistent throughout the site. Suppose in another app we want the following colors for hyperlinks

a {
    color: green;
}
a:hover {
    color: blue;
}

This will override our original hyperlink colors in the header, footer and sidebar. To avoid this we need to be more specific:

.app a { 
    color: green;
}
.app a:hover {
    color: blue;
}

If we trying to stick with BEM as much as possible this creates the problem where our (0, 1, 0) specificity BEM selectors can no longer override the app hyperlink styles. This is a problem for all BEM blocks contained in the app block. We would have to break the BEM convention or we are stuck with these colors. To solve this we can use :where().

The :where() pseudo-class function can be thought of as a function whose input is a selector and whose output is a specificity of (0, 0, 0). So by using :where() we have a great deal of control when it comes to keeping specificity low.

Suppose we put the .app selector in the :where() function. This allows us to target app without adding the .app selector weight. Thus,

:where(.app) a { 
    color: green;
}
:where(.app) a:hover {
    color: blue;
}

has the same specificity as

a {
    color: green;
}
a:hover {
    color: blue;
}

Putting everything together we get site default styles which can be locally overwritten by app default styles, both of which are always overwritten by BEM styles. So in our example we do not have to worry about the site header, footer and sidebar hyperlinks styles being overwritten by the app hyperlink styles. Moreover, we can still use BEM styles within the app as intended.

A similar application of :where() is the case when you want default styles for post content. If you rely solely on BEM you will have to either manually include a class in each selector or use something like JavaScript to inject tags with a class. Manually injecting class tags is possible, but wouldn't work well with if you wanted to use markdown since the default version of markdown does not allow adding classes. On the other hand using JavaScript requires careful planning of how to inject the tags. Is all this work for a simple default with a BEM class naming convention worth it? Perhaps, but I believe this is another good opportunity for :where(). Using :where() we can target tags in the post block without having a high specificity. For example:

:where(.post) p {
    margin-top: 0px;
    margin-bottom: 20px;
}
:where(.post) a {
    color: blue;
}

Each selector here has a specificity of (0, 0, 1). This allows us to use the cascade to override any previous defaults (so long as they have the same specificity) and if we do want to create custom BEM blocks in the post those will have the higher (0, 1, 0) specificity which allows them to easily override the default values.

Organization

So far we have spent most of our time discussing specificity and how the BEM methodology keeps specificity manageable. Let's talk a little more about the organizational benefits. This is done by giving a set of rules which naturally group CSS selectors into groups based on the chosen block. Consider a bit of code I was using before I learned about BEM.

main.main .comment-form form .item-wrapper .form-item ul.errorlist li {
    \* styles *\
}

This selector has a specificity of (0, 5, 4) and appears to be trying to reproduce the DOM structure of the element. We can reasonably expect this selector to apply to the element we intended, but this is far from optimal and would be hard to reproduce. Loosely applying BEM I was able to reduce this to

.commentForm__label ul.errorlist li {
    \* styles *\
}

Of course this selector also has a somewhat high specificity of (0, 2, 2). We could apply :where() to bring this down, but for a smaller project I was happy enough with the simplified selector. Plus, using a program like Django which generates the tags used in forms it can be a challenge to apply a class to each one. Even without perfectly conforming to BEM methodology, I can now say I am working with with block commentForm and its element label. So I can be confident that all styles in commentForm do not spill out to other blocks.

Consider a simple example for a card with a few elements.

<div class="card">
    <img class="card__image">
    <h1 class="card__title card__title--special-font">Card Title</h1>
    <p class="card__text">Text goes <em class="card__emphasis">here</em>.</p>
</div>

In this example we have a little bit of everything. We have the blocks, the elements, the grandchild elements and a modifier. Let's consider what the CSS structure would look like.

/* block */
.card {}

/* elements */
.card__image {}
.card__title {}
.card__text {}
.card__emphasis {}

/* modifier */
.card__title--special-font {}

Notice how each selector ties back to the block. This is great since you can know exactly what block you are working on without extra research. Additionally, we do not need to show the DOM structure. So our grandchild selector is simply .card__emphasis instead of .card__text__emphasis. We can also point out that special-font is the modifier for .card__title. Note that the standard BEM convention for a multi-word block, element, or modifier name is kebab-case using a single dash, however I believe camelCase is a decent alternative as talked about on CSS Tricks.

Overall, BEM helps organize our CSS in a few ways. We can keep our selectors simple. We have a logical grouping system built into our CSS with the block name. Additionally, we do not need to reflect the DOM structure in our styles. This last comment is to say that we can change for example an <h1> tag to a <h2> tag and our styles should still work without issue. This is great for code reuse where we do not always know the most appropriate semantic tag, but do know what styles we need.

Conclusion

BEM takes much of the guess work out of how to style your HTML. Following this framework we have straightforward rules to follow. Instead of thinking of components as loosely related we can now think of them as well defined blocks. Careful site structuring can allow these blocks to easily be reused in other parts of the site. This is much easier now with specificity being reined in. We no longer have to worry about an overly specific selector causing an unexpected problem when we follow BEM closely.

Of course BEM is not perfect, there are plenty of complaints; these range from making your HTML look bloated to not leaving space for default styles. Ultimately, I am happy to have found it and intend to keep working with it. I'll probably allow for some flexibility with how I use BEM as discussed when I wrote about default styles. Still the argument against default styles is valid. Why have them if you will just overwrite them multiple times over. A simple carefully crafted BEM block can get those styles set when they are needed. Still I think there are use cases such as defaults for a post so you do not have to define and include a class for every paragraph and tag in your text.

Resources

To finish out this post I wanted to include some references that I found useful in learning about BEM and specificity.