CSSのクラス名を書く前に最低限知っておくべきルール＆BEMを改良してみた



CSSはWebサービスの『ユーザー体験』を支える重要な技術で、CSSによってサービスのユーザビリティやメンテナンスコストが左右されると言っても過言ではありません。しかし、管理がずさんだったり、エンジニアがよく理解していないがために破綻しかけている現場を、筆者はこれまでいくつも見てきました。そこで、最低限これだけは知っておいて欲しいルール『**詳細度**と**命名規則**』の２点についてなるべくわかりやすく解説します。

# CSSが破綻するメカニズム
現場にCSS設計（ルール）がない状態で色んな作業者が思い思いにコーディングしていくと、いつしか「**指定したのに反映されない**」「**修正したら他の画面が崩れた**」「**影響範囲が読めず修正できない**」という状況に陥って、本来の開発が遅々として進まなくなることがあります。

## CSSの強さはランク付けされている
そもそもCSSには以下のような「詳細度」が存在し、これに基づいて優先度が決定されています。

| セレクタ・指定 | SS | S | A | B | C |
|:--|--:|--:|--:|--:|--:|
| 全称セレクタ(*) | 0 | 0 | 0 | 0 | 0 |
| 疑似要素(::before) | 0 | 0 | 0 | 0 | 1 |
| 要素型(div) | 0 | 0 | 0 | 0 | 1 |
| 属性セレクタ([type="submit"]) | 0 | 0 | 0 | 1 | 0 |
| 擬似クラス(:hover) | 0 | 0 | 0 | 1 | 0 |
| classセレクタ(.sample) | 0 | 0 | 0 | 1 | 0 |
| idセレクタ(#sample) | 0 | 0 | 1 | 0 | 0 |
| インラインスタイル(style="") | 0 | 1 | 0 | 0 | 0 |
| !important | 1 | 0 | 0 | 0 | 0 |

* それぞれのランクごとで計算し、上位のランクを超えることはできない（classセレクタを11個利用してもidセレクタ1個に勝てない）
* 詳細度が等しい場合は、後に記述されたものが優先される

### 計算例
```
div               A=0 B=0 C=1  詳細度 =   1
ul li             A=0 B=0 C=2  詳細度 =   2
ul ol+li          A=0 B=0 C=3  詳細度 =   3
.sample div       A=0 B=1 C=1  詳細度 =  11
.sample .item     A=0 B=2 C=0  詳細度 =  20
#sample           A=1 B=0 C=0  詳細度 = 100
#sample.sample    A=1 B=1 C=0  詳細度 = 110
!important        SS=1       詳細度 = 10000
```
これらを理解できていないと、CSSを反映させるために試行錯誤して時間を費やし、最終的には「ええい！important付けちゃえ！」が横行します。importantが付いた要素はその後の修正ができなくなり、WEBサービスは破綻に向かって突き進むことになります。

# 『BEM』を改良してみた
CSSでは「クラス名」のスコープが全て**グローバル**なので、クラス名同士が激しく衝突します。このため、クラス名自体の命名を工夫して衝突を防ぐ必要があるのです。（Vue.jsなどのモダンフレームワークでは`scoped`で擬似ローカルスコープにできますが、これも原理は同じで、フレームワークがセレクタ名を工夫することで衝突を避けています）
CSS設計（ルール）にはOOCSS、BEM、SMACSSなどたくさんありますが、一般に最も普及しているルールが**BEM**です。広く認知されているため、作業者のアサインがスムーズになったり、外注とのやりとりがしやすくなるといったメリットがあります。
しかし、BEMには「命名が冗長になる」というデメリットがあるため、今回はこれをより使いやすく**カスタマイズして**扱ってみましょう。

## BEMとは
BEMは、CSSをBlock（塊）・Element（要素）・Modifier（状態）で管理する考え方です。DOMを機能や役割の「塊」ごとに分けてマークアップします。
id指定や!importantを禁止して**class指定**だけで管理することで、詳細度のシガラミから解かれてシンプルに管理できるようになります。

先ず、それぞれの意味と使い方を説明します。

### Block　（ブロック）
* 要素の「塊」
* 機能や役割が判るように命名する **※ブロック名の命名が最重要です**
* ブロック名は**アッパーキャメルケース**を採用する（様々な命名を表現できますし、このブロックをしっかり命名することで、サードパーティcssとのコンフリクトを防いだり、以下の命名がシンプルになります）

### Element　（エレメント）
* 塊の中にある各要素
* とにかくシンプルに命名する
* ブロック名でユニーク化できているので、`Block__li`くらいシンプルでもOK!
 
### Modifier　（モディファイ）
* ブロックあるいはエレメントの「違う状態」を指定する場合に使用
* 状態が判るように命名する  
* `-red`のようにハイフン始まりで単独の**クラス**として指定する
**※ハイフンの直後に[数字は禁止](https://momdo.github.io/css2/syndata.html#value-def-identifier)、必ず英字にしてください**

```html
<div class="Block">
  <p class="Block__element"></p>
  <p class="Block__element -modifier"></p>
</div>
```
このように塊の親要素であるBlockを指定し、その中にElementを指定していきます。Elementの命名は`Block__element`のようにアンダーバー２つで繋ぎます。

### マークアップ例
論より実物を見てみましょう。
<img width="234" alt="スクリーンショット 2019-10-05 13.57.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365843/2a0e7f8f-5370-b8e8-8659-c68264e3b2e3.png">
↑例えばこういったアイコンカードのようなパーツを作る場合、

```html
<div class="IconCard">
  <img class="IconCard__icon" src="/images/taka.png">
  <p class="IconCard__name">たかたか</p>
</div>
```

```scss
//SCSS
.IconCard{
    display: flex;
    width: 200px;
    padding: 10px;
    border-radius: 10px;
    background: #90ee90;
    box-shadow: inset 0 -10px 10px rgba(0,0,0,.1),
    0 0 0 2px rgb(255,255,255),
    1px 1px 3px rgba(0,0,0,.3);
    align-items: center;
    @at-root{
        .IconCard__icon{
            overflow: hidden;
            width: 50px;
            height: 50px;
            margin-right: 10px;
            border-radius: 50%;
        }
        .IconCard__name{
            font-size: 16px;
            overflow: hidden;
            width: 140px;
            white-space: nowrap;
            text-overflow: ellipsis;
            color: #2c2f34;
        }
    }
}
```
このように `IconCard` というブロックを用意し、その中に`IconCard__icon`と`IconCard__name`というエレメントを記述します。クラス名から役割や機能が推測できるように命名します。

* SCSSで `@at-root` を使っているのは、**クラスの詳細度を低く抑える**ことでより管理しやすくするためです。
* エレメントは`&__icon`や`&__name`のように省略することもできますが、`.IconCard__icon`と記述した方が視認性がよく管理しやすいです。

次に下記のように「説明文」を追加する場合どのようなマークアップになるでしょう？
<img width="234" alt="スクリーンショット 2019-10-05 13.57.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365843/8a44b15a-b82b-7752-8e39-494ef720b4e9.png">

```html
<div class="IconCard">
  <img class="IconCard__icon" src="/images/taka.png">
  <p class="IconCard__name">たかたかたかたかたかたかたかたか</p>
  <div class="IconCard__description">
    ホゲホゲホゲホゲホゲホゲホゲホゲホゲホゲホゲ
  </div>
</div>
```

```scss
//SCSS
.IconCard{
    font-size: 16px;
    display: flex;
    width: 200px;
    padding: 10px;
    border-radius: 10px;
    background: #90ee90;
    box-shadow: inset 0 -10px 10px rgba(0,0,0,.1),
    0 0 0 2px rgb(255,255,255),
    1px 1px 3px rgba(0,0,0,.3);
    align-items: center;
    flex-wrap: wrap;
    @at-root{
        .IconCard__icon{
            overflow: hidden;
            width: 50px;
            height: 50px;
            margin-right: 10px;
            border-radius: 50%;
        }
        .IconCard__name{
            font-size: 16px;
            overflow: hidden;
            width: 140px;
            white-space: nowrap;
            text-overflow: ellipsis;
            color: #2c2f34;
        }
        .IconCard__description{
            font-size: 16px;
            width: 100%;
            margin-top: 10px;
            padding: 10px;
            color: #2c2f34;
            border-top: 1px solid #708090;
        }
    }
}

```

`IconCard__description` エレメントが追加されましたね。
これもクラス名を見ただけで「IconCardブロックの中にある説明に関するエレメントだ」と一目で役割が判ります。

今度は背景が「黒い」バージョンを作りたい場合はどうするでしょう？
ここで登場するのが「Modifier」です。

```html
<div class="IconCard -black">
  <img class="IconCard__icon" src="/images/taka.png">
  <p class="IconCard__name">たかたかたかたかたかたかたかたか</p>
  <div class="IconCard__description">
    ホゲホゲホゲホゲホゲホゲホゲホゲホゲホゲホゲ
  </div>
</div>
```

```scss
//SCSS
.IconCard{
    display: flex;
    width: 200px;
    padding: 10px;
    border-radius: 10px;
    background: #90ee90;
    box-shadow: inset 0 -10px 10px rgba(0,0,0,.1),
    0 0 0 2px rgb(255,255,255),
    1px 1px 3px rgba(0,0,0,.3);
    align-items: center;
    flex-wrap: wrap;
    &.-black{
        background: #000;
        .IconCard__name {
            color: #fff;
        }
        .IconCard__description {
            color: #fff;
        }
    }
    @at-root{
        .IconCard__icon{
            overflow: hidden;
            width: 50px;
            height: 50px;
            margin-right: 10px;
            border-radius: 50%;
        }
        .IconCard__name{
            font-size: 16px;
            overflow: hidden;
            width: 140px;
            white-space: nowrap;
            text-overflow: ellipsis;
            color: #2c2f34;
        }
        .IconCard__description{
            width: 100%;
            margin-top: 10px;
            padding: 10px;
            color: #2c2f34;
            border-top: 1px solid #708090;
        }
    }
}

```

`IconCard` ブロックの背景色が黒いバージョンを指定したいので、`IconCard` ブロックに対して `-black` クラス（Modifier）を付与します。
それに伴って `IconCard` セレクタ内に下記のスタイルが追加されていますね。

```scss
&.-black{
    background: #000;
    .IconCard__name {
        color: #fff;
    }
    .IconCard__description {
        color: #fff;
    }
}
```

「`IconCard` に `-black` クラスが付与されている場合は、背景を黒色（#000）にし、 `IconCard__name` と `IconCard__description` の文字色は白色(#fff)にする」という指定です。

今回は便宜的に `-black` としていますが、実務では `-primary` など**要素の役割**にもとづいて命名します。例えばサイトカラーなどをリニューアルして要素の色が青に変更された場合、 `-black` などの色の名前で指定していると全ての要素を `-blue` に変更する必要が出てきて今後の作業が大変になるからです。また、Modifierを**アッパーキャメルケース**で記述することも、ユニーク性や命名の多様性を高めるうえで有効です。

実際に触って感覚を掴んでください。Modifier（-blackクラス）を付けたり外したりするだけで、違う状態のBlockを再現できることが解るはずです。
<p class="codepen" data-height="265" data-theme-id="0" data-default-tab="css,result" data-user="koutaka" data-slug-hash="JjjjWRr" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="BEMサンプル">
  <span>See the Pen <a href="https://codepen.io/koutaka/pen/JjjjWRr">
  BEMサンプル</a> by たかたか (<a href="https://codepen.io/koutaka">@koutaka</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

![Untitled presentation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365843/7be9db4a-4977-40ff-d7d8-7e94d7b1ea29.png)

このように、サイトのパーツを役割の「塊（Block）」で分けることにより、再利用しやすく管理しやすい運用が可能となります。
現場に命名規則がなかったり、すでにあるがうまく機能していない場合は、一度これらを参考に見直してみてはいかがでしょうか。