16

SASS + BEM is pretty much a match made in heaven most of the time, but a common struggle of mine is understand how to best define BEM modifiers on an element that affects it's child elements while using SASS parent selectors.

I have the following component defined in SASS using BEM style syntax:

.card {
  background-color: #FFF;

  &__value {
    font-size: 2em;
    color: #000;
  }
}

This works well because of SASS's parent selector. It keeps relevant code organized and self-contained.

But when I need to add a modifier that alters a child element using a parent selector, the idea quickly falls apart:

.card {
  padding: 2em;

  &__value {
    font-size: 1.5em;
    color: #000;
  }

  &--big {
    padding: 2.5em;

    &__value {          // Is this going to work?
      font-size: 3em;
    }
  }
}

Nope. It generates this:

.card {
  padding: 2em;
}
.card__value {
  font-size: 1.5em;
  color: #000;
}
.card--big {
  padding: 2.5em;
}
.card--big__value {  // Wrong
  font-size: 3em;
}

It would make more sense to somehow get this selector:

.card--big .card__value {
  font-size: 3em;
}

The reason for this, is so you can simply add a modifier to the top level element and have it affect any or all of the child elements.

I've tried a couple approaches:

Use two structures

.card {
  padding: 2em;

  &__value {
    font-size: 1.5em;
    color: #000;
  }
}

.card--big {
  padding: 2.5em;

  &__value {
    font-size: 3em;
  }
}

This works (especially in this simplified demonstration) but in a more complicated set of components with many modifiers this can be a potential pain to maintain and keep bug free. Also, it would nice to continue to use SASS parent selectors if possible.

Use a variable for the element

.card {
  $component: &;  // Set the variable here

  padding: 2em;

  &__value {
    font-size: 1.5em;
    color: #000;
  }

  &--big {
    padding: 2.5em;

    #{$component}__value {  // Use it here
      font-size: 3em;
    }
  }
}

This works well. But it seems kind of silly to have to define the element as a variable. Maybe it's the only real way to do this... I'm not sure. Are there better options to how to structure this?

Jake Wilson
  • 88,616
  • 93
  • 252
  • 370
  • Good point! This is the cleanest way of nesting rather than using a hundred mixins. I consider your question as self answered. – llobet Jun 09 '17 at 06:57
  • It can be done with ONE character `&--big {` becomes `&--big & {` - check my answer for details. Hopefully useful for anyone stumbling onto this in the future – anotheruser1488182 Apr 27 '21 at 08:43

8 Answers8

6

Why not just do this?

.card {
  padding: 2em;

  &__value {
    font-size: 1.5em;
    color: #000;
  }

  &--big {
    padding: 2.5em;
  }

  &--big &__value {  
    font-size: 3em;
  }
}
Wouter G.
  • 75
  • 6
3

You can split up the modifiers in a different structure, but nested within the .card selector, like this:

.card {
  padding: 2em;

  &__value {
    font-size: 1.5em;
    color: #000;
  }

  &--big {
    padding: 2.5em;
  }

  &--big &__value {
    padding: 2.5em;
  }
}

Which will in turn produce this:

.card {
  padding: 2em;
}
.card__value {
  font-size: 1.5em;
  color: #000;
}
.card--big {
  padding: 2.5em;
}
.card--big .card__value {
  padding: 2.5em;
}

I think this is an almost perfect way, although its not perfectly nested I hope this was a help!

David Genger
  • 875
  • 10
  • 25
3

I just found a much better way to achieve this with sass, by using a variable to store the value of the parent selector you can use it at any level of nesting!

For example here I am storing the .card selector in the $this variable, and reusing it like this #{$this}

So this code

.card {
  padding: 2em;

  &__value {
    font-size: 1.5em;
    color: #000;
  }
  $this: &;

  &--big {
    padding: 2.5em;

    #{$this}__value {
      font-size: 3em;
    }
  }
}

will compile to

.card {
  padding: 2em;
}
.card__value {
  font-size: 1.5em;
  color: #000;
}
.card--big {
  padding: 2.5em;
}
.card--big .card__value {
  font-size: 3em;
}

This answer was inspired by this article on css-tricks. Thanks to Sergey Kovalenko

David Genger
  • 875
  • 10
  • 25
2

There is another pattern you could use here.

This will:
- separate actual card modifier from its elements' modifiers
- will keep the modified styles within the same elements' selector so you don't need to scroll your code up and down to see what is being modified
- will prevent more specific rules from appearing above less specific rules, if that's your thing

Here's an example:

// scss
.card {
  $component: &;

  padding: 2em;

  &--big {
    padding: 2.5em;
  }

  &__value {
    font-size: 1.5em;
    color: #000;

    #{$component}--big & {
      font-size: 3em;
    }
  }
}

/* css */
.card {
  padding: 2em;
}
.card--big {
  padding: 2.5em;
}
.card__value {
  font-size: 1.5em;
  color: #000;
}
.card--big .card__value {
  font-size: 3em;
}
gedijedi
  • 596
  • 1
  • 6
  • 7
2

You can do this by adding ONE character, no variables or mixins are needed.

&--big { becomes &--big & {:

.card {
  padding: 2em;

  &__value {
    font-size: 1.5em;
    color: #000;
  }

  &--big & {
    padding: 2.5em;

    &__value {          // Is this going to work? (yes!)
      font-size: 3em;
    }
  }
}
anotheruser1488182
  • 315
  • 1
  • 3
  • 9
1

Going through the same issue I've build a library called Superbem which is basically a set of SCSS mixins that help you write BEM declarative way. Take a look:

@include block(card) {
    padding: 2em;

    @include element(value) {
        font-size: 1.5em;
        color: #000;
    }

    @include modifier(big) {
        padding: 2.5em;

        @include element(value) {
            font-size: 3em;
        }
    }
}

Gives you:

.card, .card--big {
    padding: 2em; 
}

.card__value {
    font-size: 1.5em;
    color: #000; 
}

.card--big {
    padding: 2.5em; 
}

.card--big .card__value {
    font-size: 3em; 
}

Hope you'll find this useful!

pittkost
  • 11
  • 2
0

Your solution looks quite fine but you may also try @at-root.

tadatuta
  • 2,007
  • 11
  • 12
0
.card {
  padding: 2em;

  &__value {
    font-size: 1.5em;
    color: #000;
  }

  &--big {
    padding: 2.5em;

    &__value {          // Is this going to work?
      font-size: 3em;
    }
  }
}

You can achieve the result You wanted by the following :

.card {
  padding: 2em;

  &__value {
    font-size: 1.5em;
    color: #000;
  }

  &--big {
    padding: 2.5em;

    .card {
       &__value {          // this would work
          font-size: 3em;
       }
    }
  }
}