BEM. Like seemingly all methods on this planet of front-end growth, writing CSS in a BEM format could be polarizing. However it’s – at the very least in my Twitter bubble – one of many better-liked CSS methodologies.
Personally, I feel BEM is sweet, and I feel it’s best to use it. However I additionally get why you may not.
No matter your opinion on BEM, it provides a number of advantages, the most important being that it helps keep away from specificity clashes within the CSS Cascade. That’s as a result of, if used correctly, any selectors written in a BEM format ought to have the identical specificity rating (0,1,0
). I’ve architected the CSS for loads of large-scale web sites through the years (suppose authorities, universities, and banks), and it’s on these bigger initiatives the place I’ve discovered that BEM actually shines. Writing CSS is way more enjoyable when you will have confidence that the types you’re writing or enhancing aren’t affecting another a part of the positioning.
There are literally exceptions the place it’s deemed completely acceptable so as to add specificity. As an example: the :hover
and :focus
pseudo lessons. These have a specificity rating of 0,2,0
. One other is pseudo components — like ::earlier than
and ::after
— which have a specificity rating of 0,1,1
. For the remainder of this text although, let’s assume we don’t need some other specificity creep. 🤓
However I’m not likely right here to promote you on BEM. As an alternative, I need to discuss how we are able to use it alongside trendy CSS selectors — suppose :is()
, :has()
, :the place()
, and many others. — to achieve even extra management of the Cascade.
What’s this about trendy CSS selectors?
The CSS Selectors Stage 4 spec provides us some highly effective new(ish) methods to pick components. A few of my favorites embrace :is()
, :the place()
, and :not()
, every of which is supported by all trendy browsers and is protected to make use of on virtually any mission these days.
:is()
and :the place()
are principally the identical factor besides for the way they impression specificity. Particularly, :the place()
all the time has a specificity rating of 0,0,0
. Yep, even :the place(button#widget.some-class)
has no specificity. In the meantime, the specificity of :is()
is the aspect in its argument listing with the best specificity. So, already now we have a Cascade-wrangling distinction between two trendy selectors that we are able to work with.
The extremely highly effective :has()
relational pseudo-class can also be quickly gaining browser assist (and is the most important new characteristic of CSS since Grid, in my humble opinion). Nevertheless, at time of writing, browser assist for :has()
isn’t fairly ok to be used in manufacturing simply but.
Lemme stick a type of pseudo-classes in my BEM and…
/* ❌ specificity rating: 0,2,0 */
.one thing:not(.something--special) {
/* types for all somethings, aside from the particular somethings */
}
Whoops! See that specificity rating? Keep in mind, with BEM we ideally need our selectors to all have a specificity rating of 0,1,0
. Why is 0,2,0
dangerous? Think about this similar instance, expanded:
.one thing:not(.something--special) {
coloration: purple;
}
.something--special {
coloration: blue;
}
Despite the fact that the second selector is final within the supply order, the primary selector’s greater specificity (0,2,0
) wins, and the colour of .something--special
components will likely be set to purple
. That’s, assuming your BEM is written correctly and the chosen aspect has each the .one thing
base class and .something--special
modifier class utilized to it within the HTML.
Used carelessly, these pseudo-classes can impression the Cascade in sudden methods. And it’s these kinds of inconsistencies that may create complications down the road, particularly on bigger and extra advanced codebases.
Dang. So now what?
Keep in mind what I used to be saying about :the place()
and the truth that its specificity is zero? We are able to use that to our benefit:
/* ✅ specificity rating: 0,1,0 */
.one thing:the place(:not(.something--special)) {
/* and many others. */
}
The primary a part of this selector (.one thing
) will get its typical specificity rating of 0,1,0
. However :the place()
— and all the pieces inside it — has a specificity of 0
, which doesn’t improve the specificity of the selector any additional.
:the place()
permits us to nest
Of us who don’t care as a lot as me about specificity (and that’s in all probability lots of people, to be honest) have had it fairly good in relation to nesting. With some carefree keyboard strokes, we could wind up with CSS like this (be aware that I’m utilizing Sass for brevity):
.card { ... }
.card--featured {
/* and many others. */
.card__title { ... }
.card__title { ... }
}
.card__title { ... }
.card__img { ... }
On this instance, now we have a .card
element. When it’s a “featured” card (utilizing the .card--featured
class), the cardboard’s title and picture must be styled otherwise. However, as we now know, the code above leads to a specificity rating that’s inconsistent with the remainder of our system.
A die-hard specificity nerd might need finished this as an alternative:
.card { ... }
.card--featured { ... }
.card__title { ... }
.card__title--featured { ... }
.card__img { ... }
.card__img--featured { ... }
That’s not so dangerous, proper? Frankly, that is lovely CSS.
There’s a draw back within the HTML although. Seasoned BEM authors are in all probability painfully conscious of the clunky template logic that’s required to conditionally apply modifier lessons to a number of components. On this instance, the HTML template must conditionally add the --featured
modifier class to 3 components (.card
, .card__title
, and .card__img
) although in all probability much more in a real-world instance. That’s a variety of if
statements.
The :the place()
selector can assist us write quite a bit much less template logic — and fewer BEM lessons besides — with out including to the extent of specificity.
.card { ... }
.card--featured { ... }
.card__title { ... }
:the place(.card--featured) .card__title { ... }
.card__img { ... }
:the place(.card--featured) .card__img { ... }
Right here’s similar factor however in Sass (be aware the trailing ampersands):
.card { ... }
.card--featured { ... }
.card__title {
/* and many others. */
:the place(.card--featured) & { ... }
}
.card__img {
/* and many others. */
:the place(.card--featured) & { ... }
}
Whether or not or not it’s best to go for this strategy over making use of modifier lessons to the varied little one components is a matter of private desire. However at the very least :the place()
provides us the selection now!
What about non-BEM HTML?
We don’t stay in an ideal world. Generally you’ll want to cope with HTML that’s exterior of your management. As an example, a third-party script that injects HTML that you’ll want to type. That markup usually isn’t written with BEM class names. In some circumstances these types don’t use lessons in any respect however IDs!
As soon as once more, :the place()
has our again. This answer is barely hacky, as we have to reference the category of a component someplace additional up the DOM tree that we all know exists.
/* ❌ specificity rating: 1,0,0 */
#widget {
/* and many others. */
}
/* ✅ specificity rating: 0,1,0 */
.page-wrapper :the place(#widget) {
/* and many others. */
}
Referencing a mother or father aspect feels just a little dangerous and restrictive although. What if that mother or father class adjustments or isn’t there for some purpose? A greater (however maybe equally hacky) answer could be to make use of :is()
as an alternative. Keep in mind, the specificity of :is()
is the same as probably the most particular selector in its selector listing.
So, as an alternative of referencing a category we all know (or hope!) exists with :the place()
, as within the above instance, we may reference a made up class and the <physique>
tag.
/* ✅ specificity rating: 0,1,0 */
:is(.dummy-class, physique) :the place(#widget) {
/* and many others. */
}
The ever-present physique
will assist us choose our #widget
aspect, and the presence of the .dummy-class
class inside the identical :is()
provides the physique
selector the identical specificity rating as a category (0,1,0
)… and the usage of :the place()
ensures the selector doesn’t get any extra particular than that.
That’s it!
That’s how we are able to leverage the fashionable specificity-managing options of the :is()
and :the place()
pseudo-classes alongside the specificity collision prevention that we get when writing CSS in a BEM format. And within the not too distant future, as soon as :has()
features Firefox assist (it’s at present supported behind a flag on the time of writing) we’ll doubtless need to pair it with :the place() to undo its specificity.
Whether or not you go all-in on BEM naming or not, I hope we are able to agree that having consistency in selector specificity is an efficient factor!