こんにちは! サイボウズ フロントエンドエキスパートチームの麦島です。
CSS Modern Features、今回取り上げるCSSの機能はCSS Nesting Moduleです。
CSS Nesting Moduleは、CSS定義のネスト
次のコードはCS Nesting Moduleの記述例です。
/*
.mainクラスを持つ要素の
子孫のa要素のカラーを変更
*/
/* 従来の記述 */
.main a {
color: red;
}
/* CSS Nesting Module での例 */
.main {
a {
color: red;
}
}
CSS Nesting Moduleの利用例/階層の深い要素へのスタイル指定
CSS Nesting Moduleが活用できる例として、階層が深い構造に対してスタイルを適用するケースが考えられます。
次のような、メニュー表示におけるHTMLへのスタイル指定を例に見てみましょう。
<main>
<ul>
<li>
<a href="/top">Top<span class="icon">→</span></a>
</li>
<li>
<a href="/articles">Articles<span class="icon">→</span></a>
</li>
<li>
<a href="/search">Search<span class="icon">→</span></a>
</li>
</ul>
</main>
main
→ ul
→ li
→ a
→ span
の階層での入れ子構造となっています。このHTMLに次のスタイルを適用したいと想定します。
- スタイルは同様のマークアップ構造
( main
→ul
→a
→span
)の場合にのみ適用する a
要素がホバーされた場合にはスタイルを切り替える.icon
クラスを持つspan
要素は、親のa
要素がホバーされた場合にのみ表示する
実際の見た目は次のイメージです。
これをCSS Nesting Moduleを用いない形でCSSで実装すると、次のようなコードになります。
/* a要素の基本スタイル */
main > ul > li > a {
border: 2px solid transparent;
padding: 4px;
}
/* a要素がホバーされた場合のスタイル */
main > ul > li > a:hover {
border-color: blue;
font-weight: bold;
}
/* .icon要素は普段は非表示 */
main > ul > li > a > span.icon {
display: none;
}
/* 親のa要素がホバーされた場合.icon要素を表示 */
main > ul > li > a:hover > span.icon {
display: inline;
}
CSSの内容を見てみると、階層が深いことにより定義の重複が多く発生しています。main > ul > li > a
の部分はすべての定義で重複しており、ここでたとえばmain
要素の配下はsection
で囲いたい」
CSS Nesting Moduleを用いた場合
それでは、CSS Nesting Moduleで同じスタイルを記述した場合の例を見てみましょう。
main > ul > li > a {
/* a要素の基本スタイル */
border: 2px solid transparent;
padding: 4px;
&:hover {
/* a要素がホバーされた場合のスタイル */
border-color: blue;
font-weight: bold;
/* 親のa要素がホバーされた場合.icon要素を表示 */
> span.icon {
display: inline;
}
}
/* .icon要素は普段は非表示 */
> span.icon {
display: none;
}
}
main > ul > li > a
の部分の重複が排除されています。HTMLのマークアップ構造と同じようにCSS上でもネストして表現できるため、より直感的に記述できます。
なお、セレクタでは通常どおり擬似クラスを使えるため、:is()
や:where()
などと組み合わせることで、さらに柔軟に階層構造を表現できます。たとえば、a
要素に加えてbutton
要素でも同等のスタイルを適用したい」
main > ul > li > :is(a, button) {
/* 省略 */
}
Nesting Selector(&
)
&
)先述の例の中で、ホバー用スタイルにおいて&:hover
といった記述をしています。
ここでの&
は、Nesting Selectorと呼ばれるCSS Nesting Moduleで利用可能な新しいCSSセレクタです。Nesting Selectorは、入れ子のスタイルの中で、親ルール自体のセレクタを参照したい場合に使用します。
具体的には、例のように:hover
などの擬似クラスが付与された要素へのスタイル適用での利用が代表的です。この場合、もしNesting Selectorを省略した場合は、単純に子孫要素のセレクタとして解釈され、大きく異なる挙動となります。
次のようなケースを考えてみます。
/* Nesting Selector を使った場合 */
.link-a {
&:hover {
color: red;
}
}
/* Nesting Selector を使わない場合 */
.link-b {
:hover {
color: red;
}
}
これは、それぞれ次のCSSと同義です。
.link-a:hover {
color: red;
}
.link-b :hover {
color: red;
}
擬似クラスの手前にスペースが入っているかどうかが違いです。Nesting Selectorを使った場合については、.link-a
の要素がホバーされた場合のスタイル」.link-b
のいずれかの子孫要素がホバーされた場合のスタイル」.link-b
自体のスタイルの切り替わりを期待していた場合には、意図しない挙動となります。
At-RulesとCSS Nesting Module
At-Rulesとは、メディアクエリでの@media
やCascade Layersでの@layer
など、@
から始まるCSS機能のことを指します。
CSS Nesting Moduleでは、次のAt-Rulesをサポートしており、ネストの中で用いることができます。
@media
@container
@supports
@layer
@scope
今回は、本連載で過去に取り上げた@container
と@layer
について、CSS Nesting Moduleと組み合わせた場合の動作を見てみましょう。
@container
とCSS Nesting Module
@container
は、Container Queriesで用いられるAt-Rulesです[1]。
Container Queriesを用いて、次のスタイルを記述してみます。
- コンテナサイズが100px未満の場合には文字色を赤にする
- コンテナスタイル
(Container Style Queries) が"my-style"の場合には文字サイズを大きくする - 上記をともに満たす場合には、文字色を青くし、文字サイズをさらに大きくする
HTMLおよびコンテナに該当する要素に適用するCSSは次の内容です。サイズと--style
の値が異なる4つのコンテナを定義しています。
<div class="container container-A">
<div class="content">Text</div>
</div>
<div class="container container-B">
<div class="content">Text</div>
</div>
<div class="container container-C">
<div class="content">Text</div>
</div>
<div class="container container-D">
<div class="content">Text</div>
</div>
まず、CSS Nesting Moduleを使わない場合、期待するスタイルは次のように表現できます。
/* コンテナの定義 */
.container {
container-type: inline-size;
margin: 8px;
border: 1px solid #000;
}
.container-A {
width: 100px;
}
.container-B {
width: 80px;
}
.container-C {
width: 100px;
--style: my-style;
}
.container-D {
width: 80px;
--style: my-style;
}
/* コンテナ内のコンテンツのスタイル */
.content {
color: black;
font-size: 16px;
}
@container (width < 100px) {
.content {
color: red;
}
}
@container style(--style: my-style) {
.content {
font-size: 24px;
}
}
@container (width < 100px) and style(--style: my-style) {
.content {
color: blue;
font-size: 32px;
}
}
すると、次のような見た目となります。
期待通り動作していますが、.content
のスタイル定義は重複しています。また、各Container Queriesと.content
の関連がわかりづらいのも課題となり得ます。定義場所が離れていたり、@container
内に.content
以外のスタイルを定義したりしてしまうと、関連がより一層あいまいとなり、保守性が低下します。
では、CSS Nesting Moduleを使って表現した例を見てみましょう。
/* コンテナの定義 */
/* 〜同様のため省略〜 */
/* コンテナ内のコンテンツのスタイル */
.content {
color: black;
font-size: 16px;
@container (width < 100px) {
color: red;
}
@container style(--style: my-style) {
font-size: 24px;
@container (width < 100px) {
color: blue;
font-size: 32px;
}
}
}
CSS Nesting Moduleを用いない場合と比較してみると、.content
の記述やstyle(--style: my-style)
の定義の重複が排除され、簡潔に表現できています。また、.content
のスタイル定義の中にすべての@container
定義が集約されており、要素とContainer Queriesの関連が明確になっています。コンポーネントのスタイル定義では関連するスタイルを一ヵ所に集約しておくことで責務が明示的になり保守性が高まるため、その点でCSS Nesting Moduleと組み合わせるメリットはあると言えるでしょう。
@layer
とCSS Nesting Module
@layer
は、Cascade Layersで用いられるAt-Rulesです[2]。
Cascade Layersでは、.
でレイヤー名をつないで宣言することでサブレイヤーを定義できます。
たとえばbase
レイヤーのサブレイヤーとしてbase.
レイヤーとbase.
レイヤーを定義しています。
@layer base.global {
* {
color: gray;
}
a:hover {
color: blue;
}
}
@layer base.default {
a {
color: red;
}
}
これらはあくまでもbase
レイヤーに属するサブレイヤーであり、base
レイヤー自体の優先度にも大きく影響を受けます。仮にあらかじめ@layer base, page;
といった形でレイヤーを宣言していた場合、レイヤーの優先順序は次のとおりです。
page
レイヤーbase
レイヤーbase.
サブレイヤーdefault base.
サブレイヤーglobal
base.
レイヤーとbase.
レイヤーに宣言されたスタイルよりもpage
レイヤーに宣言されたスタイルのほうが優先されます。
このCascade Layersでのサブレイヤーの宣言について、CSS Nesting Moduleを用いると次のように記述できます。
@layer base {
@layer global {
* {
color: gray;
}
a:hover {
color: blue;
}
}
@layer default {
a {
color: red;
}
}
}
.
でつないだ形での宣言よりも、レイヤー・base.
を複数回記述する必要もありません。筆者の意見としては、CSS Nesting Moduleが利用できる環境であれば積極的に活用していくほうが望ましいと考えています。
ブラウザでのサポート状況
今回紹介した機能について、本記事掲載時点でのメジャーブラウザにおける利用可能バージョンは次のとおりです。これ以降のバージョンであれば利用可能です。
機能 | Chrome | Edge | Safari | Firefox |
---|---|---|---|---|
CSS Nesting Module | 120 | 120 | 17. |
117 |
なお、今回紹介したCSS Nesting Moduleは、本記事掲載時点では仕様的にはWorking Draftであり、今後仕様が変更される可能性がある点についてはご留意ください。
まとめ
CSS Nesting Moduleを用いると、より関連性の近いスタイルの依存・