If you’re a web developer, you probably know the pains of maintaining CSS. Those first few lines of styling that you write come so naturally, but one, two, three, ten pages in, you feel overwhelmed by the number of classes and selectors sprinkled throughout your code.
This common experience led to the rise of BEM, a set of conventions for managing stylesheets. For the unfamiliar, BEM stands for Block Element Modifier. This acronym contains the concepts that make up BEM.
These are the “groupings” of a set of related styles. They are often applied to the roots of components. For instance, a user card’s wrapping element is given the
These are the “children” of your block. They are necessarily tied to their containing block (e.g. the name of the user in a div inside the user card is given
user-card__name in the style of
These are the “customizers” of your blocks and elements. If you want to change the base styling, you can add a modifier class, following the pattern
<div class="user-card user-card--light">
Note that BEM-style CSS is very intentional about not mirroring the structure of markup. Each element can have only one parent block. In the example below, a
foo__bar__baz class in place of the
foo__baz class would be considered illegal in BEM.
One thing I’ve noticed as an Angular developer is that even though Web Components have bought us freedom and modularity in how we structure our applications, we as developers tend to retain our old, broken patterns of styling.
BEM is a very useful convention and it brings some sanity to the chaos of CSS, but with the advent of web components BEM conventions somewhat fall apart. Web frameworks like Angular allow the developer to set the scope of their CSS, ensuring it doesn’t escape the confines of their component. This is incredibly helpful when writing clean, modular code, and indeed, this behavior is almost always a good one. But BEM begins to struggle when thrown into situations with restrictions on CSS scope and abstraction of markup.
Take the following example. I have a
page class that also has a modifier class,
page--medium, that sets the width of the page. For standard HTML markup this works just fine.
<div class="page page--medium"></div>
Our classes can even be used in different sections of our template.
<div class="page page--medium"></div>
But what happens when we use the modularity of web components to turn that ‘other-pages’ section into its own element?
<div class="page page--medium"></div>
This raises two issues:
- How do we apply the
page--mediumclass programtically to the inside of
<other-component>from its containing component?
- How do we give
other-componentaccess to the styles of the
.pagenow a global selector? If so, we’ve broken much of the modularity that web components work so hard to give us. In order to have access to BEM classes globally, they need to be provided outside of individual component scopes. While this is totally possible and an approach taken by many developers, it breaks the idea of individual components owning styles.
I’m going to introduce the styling structure I’ve been employing with my Angular apps and return to the example problem in a momment.
<my-button light large>Hello World!</my-button>
The above component demonstrates the pattern I’ve adopted for the majority of my own component styling needs. This may seem like a lot of code to present up-front, but the fundamentals of it are actually quite simple.
Ultimately this pattern is based on the idea that attributes are a very powerful way of abstracting away styling that removes many of the use cases for BEM. Rather than reading classes directly off the host, we use the powerful Angular
@Input decorator to read attributes instead.
In the example above, the
input method on our component class checks for existence of the attribute, which in turn selectively applies a class to the host element. This allows us to take input beyond just a binary “class present” / “class not present”.
Note: One might be tempted to simply read attributes directly off the host with a CSS attribute selector
:host[light]rather than having an intermediate step that applies a class. While this is a possibility, I’d caution against it. Just like any well-constructed API, interactions with objects should in general occur through publicly exposed methods (our attributes), rather than direct modification of the object itself. This allows us to refactor the “API” of our component without breaking styles.
For example, it’s common in BEM to have loads of modifier classes for individual colors (
<my-button light color="blue">Hello World!</my-button>
Overall, this results in more natural interactions with our components. After all, HTML elements are customized with attributes, so why shouldn’t our custom elements be as well? Such a pattern also lends itself to programatic styling to a much greater extent than is possible with CSS alone.
The users of our components need not be concerned with the details of how classes affect our component’s styling - they just apply attributes and the component does the rest. We can alter markup this way, and do all sorts of crazy things that wouldn’t be possible with pure CSS, while still maintaining an “add the stuff you need”-style API.
One limitation that the astute reader might have noticed is that with the above code, elements cannot be added to other elements in the same way that CSS classes can. Luckily Angular has us covered with attribute selectors.
<button my-button light>This works.</button>
This pattern gives us great flexibility in where our components are applied. We can even add restrictions on the elements they can affect!
Web components also bring with them some pretty neat additions to CSS selectors using the Shadow DOM. Watch this:
/* button.css */
Wait back up a second - is that a parent selector?
Yep! The much-coveted CSS parent selector exists when using the Shadow DOM. The
:host-context selector selects a host based on a matching parent element. That lets us style our components based on the context of parent components while maintaining separation of concerns. Note that this amazing selector is available to anyone using the Shadow DOM (including React users) and is not an Angular-specific concept.
One pattern I’ve taken to is applying a
light attribute to the color-inverted sections of my code. Then my color-sensitive components query upward for this attribute and change their style, allowing me to turn this:
Now that we’ve introduced the fundamentals of this code pattern, lets return to the problem presented above. We simply create a
page attribute component
<div my-page medium></div>
This page component is the only owner of the styles associated with the old
page class. If we need to apply these styles to an element, we just annotate the tag with it. This maintains the modularity of styling that we so desperately want.
For example, on our
other-component from above:
This takes care of the two problems with BEM that we presented above quite handily.
As you can see, using the inherit compartmentalization built into web components gives us styling flexibility without the weighty concerns of how our classes will cascade or who will own them. We can use attributes in their place, quickly removing the need for the BEM concepts of blocks, elements, and modifiers. While BEM could potentially be a useful naming convention for classes within a component, with bite-sized, single-responsibility components BEM is no longer needed to keep your styling sane.
In terms of flexibility and maintainibility, I’ve had great success in the projects that I’ve chosen to use this styling strategy. Have questions? See a problem? Let me know in the comments below.