BEMに頼らなきゃいけないときに心がけていること
はじめに
そもそも2021年にまだBEMを使用していることについて
-
Railsのアセットパイプラインや他のMVCフレームワークなど、CSS Modules/CSS in JS的な運用が難しい案件はまだまだある
- 実際にはwebpackerなりpostcssなりでしっかり設定すればできるはずだが、そこの決裁権がないことがほとんど
- 上述の理由であまり知見がない、という恥ずかしい事情もある
- 単に古い案件の保守がある
- React/Vueの案件ではコンポーネントファイル内でスコープができるため、BEMを用いる必要がないので採用してない
なぜ今回まとめようと思ったか
- 引き継ぎで自分の設計したCSS周りの仕様をイチから説明する必要がでてきた
- 「なぜここがこうなのか」にはそれぞれ明確な理由があるが、それって案外自分だけで把握してることかもしれず、設計をみただけで理解されやすいものではないと思った
そもそもCSSにおける行儀を意識しないと話ができない
- カスケードしてしまうことによる副作用がつらい
- 外部ライブラリの行儀が悪いことによる副作用がつらい
- 詳細度のコントロールが必要になる仕様がつらい
- position:absolute/fixedやflexbox、gridなど、必要だけどDOMの構造と密結合なレイアウトがつらい
これらのつらさをできる限り低減するための方策として、CSS設計なり命名規則(今回のメインであるBEMなど)を活用する必要があることをまず説明しておく必要がある
が、今回そこまで入れると壮大になるので割愛
コンポーネント志向という考え方を共有しないと理解されづらい
- BEMはまずBlockというひとかたまりのコンポーネントがあり、その中で子要素としてのElementがある、という概念なので、そこをまず理解してもらう必要がある
- Atomic Designを先に知ってもらうのがよさそうだが、あれを忠実に体現するのもまた地獄なので難しい
総則
MindBEMdingを採用
- 汚いといわれがちだが、結局クラス名のユニーク性を担保しやすいのでメリットのほうが大きい
- 少なくともCSS(SASS)側は
&
の適切な使用によって汚くはならない -
HTML側はHamlなりSlimなりを使うことですこし見通しがよくなるが、汚いものは汚い
- 慣れればよい、といろいろなところで言われるが実際そのとおりと言うしかない
- 過剰なModifier付与を避ければなんとか許容できるようにはなった
Modifier入りのクラスはModifierなしのクラスと併記する
-
これによってSASS内で
@extend
だの、継承のためだけの@include
だのを使わなくてよくなる- 特に
@extend
はメディアクエリ絡みの制約があって使いづらいため避けたかった
- 特に
- ModifierはあくまでBlockのバリエーションであるという意味性からしても、原型としてのBlockを併記することは宣言的でありわかりやすい
-
haml/slim記法でいうところの
.block.block--modifierA.block--modifierB.block--modifierC
みたいな大変なことになる場合もあるが、これはむしろコンポーネントを分けるサインと捉えていて、こんな冗長になるなら切り分けましょう、という考え方
Elementの入れ子は行わない
- BEMのメリットである構造の明確化が損なわれ、過剰に複雑化してしまう
-
.block__elementA__elementBinA__elementCinBinA
は素直に
.block__elementC
にすればいいじゃない
よって構造体は以下の6種類に限定される:
.block
.block__element
.block__element--modifier
.block--modifier
.block--modifier__element
(非推奨).block--modifier__element--modifier
(非推奨)
下ふたつを非推奨としたのは「Modifierに依存したコンテクストが生まれる」のが原因で、その問題点はそもそものCSSの仕様とぶつかって大変だとかそういう話になってくるのでここでは行儀がよくないと言うにとどめておくが、個人的には下ふたつは両方とも.block__element--modifier
に統一するのがシンプルで良いと考えている
細則
SASSを使う、そしてSASS記法を使う
- BEMと直接関係はないが、セミコロンだの波括弧だのを省きたい
- インデントによる構造管理は記述の自由度を制限できるので、汚い書き方をされづらい
SASS記法におけるインデントレベルとBEMの構造に一定の関連をもたせる
-
上述の「Elementの入れ子は行わない」ことで規定された6種類の構造体は以下のようなインデント構成で記述される:
.block &__element &--modifier &--modifier &__element // 非推奨 &--modifier // 非推奨
特に
.block__element
は必ず1インデント、.block__element--modifier
は必ず2インデントになることが重要で、これによってsassファイルを目視した際に構造を把握しやすい
なるべくすべての要素にBEM形式のクラスを付与するが、厳密化しすぎない
- 要素にスタイルをあてるのは行儀が悪いので、面倒でもコンポーネント内部の要素すべてにBEM形式のクラス名を付けたい
-
だがいくらなんでも冗長性が高くなりすぎるもの、DOM構造がカチカチに固定されるものは例外的に許容したい
-
その場合は親子セレクタを用いることでなるべく影響範囲を狭める
table.block > thead, > tbody, > tr th, td
-
&
をElementやModifier名の一部として用いない
-
たとえば
.block__element
と.block__element-name
があったとして.block &__element &-name
のようにすると、上述のインデントレベルとの関連性が崩れてしまうため、この場合は素直に以下のように分ける
.block &__element &__element-name
-
&
の用法としては以下にまとめられる:.block &, > img // 自身および自身の子孫セレクタに影響を及ぼす場合 &:hover, &:focus // 疑似クラス &::before, &::after // 疑似要素 &.is-active // JavaScript等によって動的に付与されるクラスをコンテクストとする場合
同一のメディアクエリは1コンポーネントにつき1回の記述で済ませる
-
そもそも
@media
を直で書きたくないので以下のようにmixinを作っているが、直書きでも同様$breakpoint_md: 768px @mixin min-screen($breakpoint: $breakpoint_md) @media screen and (min-width: $breakpoint) @content
-
上記mixinを用いる場合、どの階層でも用いることができるが、記述がバラケて追えなくなるので一箇所にまとめる
- インデントをなるべく統一する意味でも複数の階層に記述されるべきではない
.block__element
と.block--modifier
と@media
の記述順を統一する
- これらはともに1インデントで記述されるため、パッと見で見間違えるリスクが残る
.block__element
と.block--modifier
はどの順でも問題ないが、記述順を統一しておくことでリスクを低減する- 余裕があればコメントで区切る
- いっそ
.block--modifier
をインデントなしで書いてしまうのもアリだとは感じているが、.block
に内包されるものであることを宣言的に示せるので上記のようにしている @media
(およびメディアクエリ用mixin)は最下部にまとめる
まとめると下記のようになる:
.block
...
&--modifier
...
&__element
...
// mixinを用いる場合
@include min-screen
...
&--modifier
...
&__element
...
// @mediaを直で書く場合
@media (min-width: 768px)
.block
...
&--modifier
...
&__element
...