Sass で BEM 的なことをしているときに子要素を他のセレクターの下に潜り込ませたい

ふとしです。

BEM の階層が深い要素を、他で定義した要素の下に潜り込ませたいという話です。

まず解決法は以下です。@at-root で BEM 用のネストを外しつつ、引数として BEM 用のセレクターを取って潜り込ませます。

@mixin form_control_plus($selector)
  @at-root
    input[type="text"].form-control
      &#{$selector}
        @content
.main-content
  &__main-list
    &__item
      @include form_control_plus(#{&}__big-fat-control)
        height: 100px
input[type="text"].form-control.main-content__main-list__item__big-fat-control {
  height: 100px;
}

そんなわけで後は日記です。

Sass で BEM 的

以下のような感じで定義して

.main-content
  &__main-list
    margin: 0

    &__item
      color: #fff

以下のような CSS を得るみたいなやつです。

,main-content__main-list {
  margin: 0;
}

.main-content__main-list__item {
  color: #fff
}

よくある問題

他でグローバルに定義したセレクターが強すぎて負ける場合があります。

こういうことをしていると

input[type="text"].form-control
    height: 40px;

こんな定義をしても

.main-content
  &__main-list
    margin: 0

    &__item
      color: #fff

      &__big-fat-control
        height: 100px
<input type="text" class="form-control main-content__main-list__item__big-fat-control">

height: 40px が勝ちます。

アドホックに解決

そこで以下のような解決法もありますが、

.main-content
  &__main-list
    margin: 0

    &__item
      color: #fff

input[type="text"].form-control
  .main-content
    &__main-list
      &__item
        &__big-fat-control
          height: 100px

これは二重になる上にめんどくさいので、今回の本題です。

root から定義する mixin を定義する

mixin はセレクターとして使える文字列を引数に取れます。さらに、配下のブロックを @content として利用できるので、それらを使って強いセレクターの追加セレクターとして定義します。

@mixin form_control_plus($selector)
  @at-root
    input[type="text"].form-control
      &#{$selector}
        @content
.main-content
  &__main-list
    margin: 0

    &__item
      color: #fff

      @include form_control_plus(#{&}__big-fat-control)
        height: 100px

これで以下のような CSS が得られるので、height: 100px が有効になりました。

input[type="text"].form-control.main-content__main-list__item__big-fat-control {
  height: 100px;
}