Your friends at Viget present Extend, a Code & Technology Blog

BEM, Multiple Modifiers, and Experimenting with Attribute Selectors

The FEDs here at Viget love using BEM syntax for our CSS. It enables us to adhere to an expressive naming convention which helps in hand-offs, code reviews, and just coding faster. However, the more abstract a design becomes, the more you need multiple modifiers for a single design element. You can apply each specific modifier, but that can quickly become unwieldy.

Note: If you’re not familiar with the BEM naming convention, I strongly suggest taking a look at Harry Roberts' post on the matter.

How We Currently Do Multiple Modifiers

If we wanted to apply multiple modified styles of a block element—let's say a button—our markup would have to look like this.

<button class="button button--blue button--large button--rounded">I’m a button!</button>

There's nothing wrong with this markup, but it does suffer from being anti-DRY. We know that, when we apply the BEM syntax, a modifier starts with "--". Rewriting "button" each time seems unnecessarily verbose. Luckily, attribute selectors can help.

Chaining Attribute Selectors

Thanks to the long supported CSS attribute selectors, we can extend the use of BEM even further to allow for multiple selectors. How so? Take the example below:

[class^="button"][class*="--blue"] {
    background: #00f;
}

The above CSS looks for an element that starts with a class of button that also contains --blue. An example of what this CSS rule would target is:

<button class="button--blue">I’m a button!</button>

Taking It Further

Let’s get a little more creative and apply this to a more complex real world scenario. Let’s say I had multiple modifiers (as I often do for buttons) applied to a single element like so:

<button class="button--blue--large--rounded">I’m a button!</button>

I could target this and apply the “blue”, “large”, and “rounded” styles to this button using the follow.

[class^="button"] {
    padding: 1em 2em;
}

[class^="button"][class*="--blue"] {
    background: #00f;
}

[class^="button"][class*="--large"] {
    font-size: 36px
}

[class^="button"][class*="--rounded"] {
    border-radius: 10px;
}

That’s pretty ugly to read and a pain to write. But with the help of the newest version of SASS we can use a custom mixin we can clean it up a bit:

/* Multiple Modifier */
@mixin mm($modifier) {
    $len: str-length(#{&}); /* Get parent string length */
    $parent: str-slice(#{&}, 2, $len); /* Remove leading . */

    @at-root [class^="#{$parent}"][class*="--#{$modifier}"] {
        @extend .#{$parent};
        @content;
    }
}

We can then write:

.button {
    padding: 1em 2em;
   
    @include mm(blue) {
        background: #00f;
    }
   
    @include mm(large) {
        font-size: 36px;
    }
   
    @include mm(rounded) {
        border-radius: 10px;
    }
}

The mm mixin grabs grabs the passed parameter and appends it to the parent with a "begins with" selector which then generates equivalent CSS. Click here for proof.

Drawbacks

Like any new technology or experiment, there are a couple drawbacks, most notably:

  1. Attribute Selectors are a little slow (but barely).
  2. You're creating a class name that isn't explicately written anywhere except in the markup, making it harder to debug and track down in your stylesheets.
  3. Additional classes must be added to the end of the class— ie. <button class="button--blue--rounded clearfix"> —since this targets class names using the "starts with" selector.

Should We Do This?

Probably not, though the experiment is cool. My general rule of thumb: if you can’t CMD + F for a class in your pre-processed stylesheets that was output in the markup, then you’ve gone too far. Obviously, for situations like this, I believe it's totally fine. But keep in mind: the further we walk down the path of CSS pre-processors, the more we can blur the line between readible verses maintainable code.

Can you think of other uses for chained attribute selectors? Let us know for great justice in the comments below!


Get More From Viget

Subscribe to get our monthly newsletter and occasional special announcements.