コンテキストに応じたスタイル記述

古より、コンポーネントがどこの配下に設置されるかによって振る舞いを変えたいという願いが存在する。

よく知られる古来からのやり方が以下。

.button {
	color: black;
}

.header .button {
	color: red;
}

.card .button {
	color: blue;
}

これだけ見る場合は単純だが、要素が増え階層構造が深くなるほど詳細度が深くなり、保守が難しくなっていく。これにコーダーは長く悩まされてきた。

これに対する現代的なアプローチを考える。

:where()を使う

.button {
  color: black;
}

:where(.header) .button {
	color: red;
	/* header内の.buttonの文字は赤くなる */
}

:where(.card) .button {
	color: blue;
	/* card内の.buttonの文字は青くなる */
}

:where() を用いることで詳細度を上げずに条件を指定できる。SCSSであればより見やすい形に書ける。

.button {
  color: black;
  
  :where(.header) & {
		color: red;
	}
	
	:where(.card) & {
		color: blue;
	}
}

上記の例だと、コンポーネントのスタイル記述に条件をぶら下げていく書き方となる。
他にも以下の様な記述方法がある。

.button {
  color: black;
}

.header {
	:where(&) .button {
		color: red;
	}
}

.card {
	:where(&) .button {
		color: blue;
	}
}

コンパイル後のcssは同じ構文になるが、この記述方法の利点は、.buttonコンポーネントのスタイル記述は独立させつつ、配置場所のコンポーネントスタイル記述ごとに.buttonのスタイルを上書きする管理方法になる点である。

カスタムプロパティを使う

.button {
  color: var(--button-color, black);
  /* 文字色を司るカスタムプロパティと初期値を用意。デフォルトのボタン文字色は黒 */
}

.header {
	--button-color: red;
	/* header内ではカスタムプロパティは上書きされ、.button文字色は赤くなる */
}

.c-card {
  --button-color: blue;
  /* card内ではカスタムプロパティは上書きされ、.button文字色は青くなる */
}

上記の例を解釈すると、buttonのcolorはCSSカスタムプロパティ--button-colorに従うということになる。buttonのスタイルは、配置される場所のコンテキストに左右されず一貫性を保つ続けるというのが特徴である。詳細度は一定のまま、保守性が高い。難点を挙げるとすれば、CSSカスタムプロパティのDOMスコープという特徴から、DOM構造を含めた保守難易度が高い点がある。CSSカスタムプロパティは記述も冗長になりがち。命名規則で悩むことになりそうではある。

逆に特定要素を含む親のスタイルを変える

:has()を使う。

.card {
	display: block;
}

.card:has(.button) {
  display: grid;
}

:has()を使うことで、第一引数に入れた要素が存在する場合のスタイルを記述することができる。上記の例だと、.buttonが存在するcardはdisplay値が変わる。見方を変えると、今まで難しかった特定要素の親のスタイルを指定できるということになる。

DOM構造以外の条件指定に関する期待

現状CSSにおける条件分岐はDOM構造に基づく条件しか書けない。しかしかつてはそれも選択肢はかなり少なかったが、昔に比べて利用可能な選択はかなり広がってとても楽になった。今後CSSにカスタム関数やif文が追加されれば、DOM構造以外にもプロパティ値や変数の比較で条件を作れるようになり、さらに簡単かつ複雑なコンテキスト下での処理も可能になることを期待したい。