知っておきたい、CSS Nesting仕様 第1回 基本のネスト構文

CSSのルールをネストできるCSS Nesting仕様について、基本的な構文を紹介します。また、Sassとの違いについても触れます。

発行

著者 矢倉 眞隆 フロントエンド・エンジニア
知っておきたい、CSS Nesting仕様 シリーズの記事一覧

はじめに

昨今のCSSの進化はすさまじく、夢見ていた機能が続々と提案・実装されています。その中の1つがCSS Nesting仕様です。2023年に各ブラウザーで実装され、使えるようになりました。

ネストといえば、SassなどのCSSプリプロセッサーで活用していた方も多いのではないでしょうか。今回は、プリプロセッサー実装との比較も交えながら、CSSのネイティブのネストについて紹介します。

CSS Nesting仕様とは

CSS Nesting仕様は、CSSのルールを別のルールの中にネストできるようにする仕様です。

CSSのルールとは何かというと、たとえばいつも書いているセレクターとブロック、あれがルールです。

CSSのスタイルルールの一例

.note {
  border: 1px solid #ccc;
  color: #666;
}

CSSの仕様では、このセレクタと直後のブロックをまとめたものをスタイルルール(またはルールセット)と呼びます。

CSS Nesting仕様は、これらスタイルルールといくつかの@ルールを、別のスタイルルールの中に書けるようにします。これにより、スタイルのまとまりや管理性を高められます。

スタイルルールのネスト

ではまず、簡単なスタイルルールのネストから紹介しましょう。たとえばこれまで、こんなCSSを書いていたとします。

横並びのナビゲーションのスタイル

/*
<ul class="HeaderNav">
  <li><a href="...">ホーム</a></li>
  <li><a href="...">事業紹介</a></li>
  ...
</ul>
*/
.HeaderNav {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  gap: 10px;
}

.HeaderNav li { /* ... */ }

.HeaderNav a {
  padding: 0.5em 1em;
}

横並びのナビゲーションにするため、.HeaderNavをつけたul要素のデフォルトスタイルを打ち消したうえで、Flexboxで横並びにしています。ナビゲーション内のリンクは押しやすいように、パディングをつけています。

CSS Nesting仕様が実装されたブラウザーにおいて、このコードは次のように書き換えられます。

liとaをネストする

.HeaderNav {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  gap: 10px;

  li { /* ... */ }

  a {
    padding: 0.5em 1em;
  }
}

li要素とa要素のスタイルを.HeaderNavにネストしています。ネストした2つのルールはもともとの.HeaderNav li.HeaderNav aと同じ意味になりますが、ついていた.HeaderNavがなくなりすっきりしました。

子孫要素以外のネスト

ネストしたルールのセレクターは通常、親のブロックの子孫セレクターとして解釈されます。子孫セレクター以外のブロックをネストしたい場合はどうなるでしょうか。

先ほどの例は、サイトヘッダーにある単純な横並びのナビゲーションリストでした。しかし、サイトの規模が大きくなりコンテンツが増えたので、ドロップダウンのナビゲーションにしたいなんて要望が出てくるかもしれません。

ドロップダウンのリンクが増えてしまった

<ul class="HeaderNav">
  <li><a href="...">ホーム</a></li>
  <li><a href="...">事業紹介</a>
      <ul class="HeaderNav__category">
        <li><a href="...">アプリ開発</a></li>
        <li><a href="...">サイト運用</a></li>
        <li><a href="...">マーケティング</a></li>
      </ul>
  </li>
  ...
</ul>

li要素の中にさらに、カテゴリーナビゲーション用のul要素が入ってしまいました。こうなると先ほどの.HeaderNav li.HeaderNav aというセレクターでは不都合が出てきます。なぜならどちらも、.HeaderNav直下のli要素と、そのli要素直下のa要素だけに対するスタイルを意図していたからです。

カテゴリーナビゲーションのli要素とa要素を避けるためには、.HeaderNav > li.HeaderNav > li > aと、直下の要素であると書かないといけません。これはCSS Nesting仕様でどう表現できるのでしょうか。

子要素に限定したネスト

.HeaderNav {
  /* 省略 */

  > li { /* ... */ }

  > li > a {
    padding: 0.5em 1em;
  }
}

はい。子要素を選択する>結合子からネストを始めることで、.HeaderNavの子要素のli要素、そしてその子要素のa要素であると表現できます。

もちろん>だけでなく、+~といった他の結合子も使えます。

補足:結合子

結合子に関しては、次の記事なども参考にしてください。

ネストの多重ももちろんできる

先ほどの例ではa要素のセレクタを> li > aと書いていましたが、多重にしたネストとして表現もできます。

ネストの中にネストする

.HeaderNav {
  /* 省略 */

  > li {
    /* ... */

    > a {
      padding: 0.5em 1em;
    }
  }
}

ネストしたli要素のブロックの中に、a要素のルールセットをさらにネストしました。ネストの多重化には書き方の好みも出そうですが、仕様としてはこちらも違和感なく受け入れられるものでしょう。

コラム:どれくらいネストできるのか

執筆時点の2024年2月において、ネストの階層の深さに制約は課されていません。しかし、深すぎるネストによってパフォーマンスが低下する懸念も実装によってはあるようです。このため深さの制限は実装依存とされる予定です。

すでにWebKitでは128段階までという限界が設定されています。とはいえ、いたずらに遊んだり、ブロックの閉じ忘れがあったりしなければ、ここまで深いネストになることはないでしょう。

ネスト元を表すネスティングセレクター

.HeaderNav内のリンクはパディングしか指定していないので、もう少しナビゲーションとしてのインタラクティブさがほしいところです。

リンクには、訪問済みやホバーした時、クリックした時、フォーカスした時といった状態があります。これらのスタイルを指定するには擬似クラスを使います。

リンクの擬似クラス

a:visited {
  color: #60c;
}

a:hover {
  background-color: #eee;
}

a:active {
  color: crimson;
}

a:focus {
  outline: 3px solid #06c;
}

a要素の擬似クラスが連続しているので、これらもネストして書きたくなります。しかし、次のように書いてもうまくいきません。

リンクの擬似クラスを書いたつもり

a {
  :hover {
    background-color: #eee;
  }

  :focus {
    outline: 3px solid #06c;
  }
}

一見よさそうに見えますが、これは意図したスタイルにはなりません。こう書いてしまうと:hover:focusはa要素の擬似クラスではなく、a要素の子孫要素の擬似クラス、つまりa *:hovera *:focusという意味になるからです。

CSS Nesting仕様では、ネストしたルールのセレクターは通常、その親のルールの子孫要素のセレクターとして解釈されます。では、a要素の擬似クラスとしたい場合はどう書けばいいのでしょうか。

ここで登場するのが、CSS Nesting仕様で新しく定義されたネスティングセレクターです。ネスティングセレクターは&を使って表します。これは、ネストされた内側のルールから、包含する外側のルールのセレクターを参照する際に使われます。

では、ネスティングセレクターを使い、リンクの擬似クラスを適切にネストしてみましょう。

ネスティングセレクターでaの擬似クラスにできる

a {
  &:hover {
    background-color: #eee;
  }

  &:focus {
    outline: 3px solid #06c;
  }
}

&:hover&:focusと書くことで、無事a:hovera:focusと同じ意味にできました。

ネスティングセレクターが使えるところ

ネスティングセレクターは擬似クラスだけではなく、擬似要素や属性セレクターにも使えます。

属性セレクタと擬似要素にも使える

a {
  padding: 0.5em 1em;

  /* アイコンをつけたい */
  &::before {
    display: inline-block;
    content: "🌐";
  }

  /* 外部リンクのしるし */
  &[href^="http"]::after {
    display: inline-block;
    content: "⤴";
  }
}

擬似クラスや擬似要素はたくさんあります。「CSSセレクター総ざらい」で紹介されていますので、参考にしてください。

また、最初のほうに説明した結合子を使う例においても、ネスティングセレクターを使用できます。子孫要素の例、>を使った子要素の例、そして多重のネストの例を、ネスティングセレクターを使って書き換えてみましょう。

ネスティングセレクターと結合子

/* 子孫要素のliとa */
.HeaderNav {
  & li { /* ... */ }
  & a { /* ... */ }
}

/* 子のli要素に限定してネストする */
.HeaderNav {
  & > li { /* ... */ }
  & > li > a { /* ... */ }
}

/* ネストの中にネストする */
.HeaderNav {
  & > li {
    & > a { /* ... */ }
  }
}

ネスティングセレクターを使った例は、結合子から始める例と比べると、どのセレクターに対してネスティングルールが適用されるかがわかりやすくなります。

CSS Nesting仕様とSassとの違い

ここまで、CSS Nesting仕様の機能や構文について簡単に説明してきました。

CSS Nesting仕様の構文は、Sassなどのプリプロセッサーを使っていた方には特に違和感なく受け入れられるでしょう。仕様の策定においても、プリプロセッサーに馴染んでいた開発者が受け入れられやすい構文にすることが意識されています。

しかし、プリプロセッサーの機能がすべて取り入れられているわけではありません。

たとえばSassでは、結合子の直後でもブロックのネストを始められますが、これはCSS Nesting仕様にはありません。

Sassでできるネスティング

// どちらも.HeaderNav > liのスタイルになる

// 結合子の直後にブロックが始まる
.HeaderNav > {
  li { /* ... */ }
}

// 結合子だけのブロックも書ける
.HeaderNav {
  > {
    li { /* ... */ }
  }
}

&についても、Sassとまったく同じ機能を提供しているわけではありません。たとえばSassでは、ブロックの親のセレクターに同じ文字列がある場合、その部分文字列を&で記述できます。

Sassの親セレクターではこう書ける

.HeaderNav {
  &__category {
    // .HeaderNav__categoryにコンパイルされる
  }
}

BEMのような命名規則を使っている場合、この機能が魅力的に感じる方もいるでしょう。しかし、この機能はCSS Nesting仕様にはありません。ネスティングセレクターの&はあくまでセレクターを参照するものであり、文字列の置換機能ではないからです。

CSS Nesting仕様はプリプロセッサーの構文や機能を参考にしていますが、CSSの文法と実装面で無理のない部分だけ取り入れたものです。Sassに慣れている方がCSS Nesting仕様に移行する場合は、こうした違いをおさえておくとよいでしょう。

まとめ

今回は、CSS Nesting仕様の基本的な構文について紹介しました。

  • CSS Nesting仕様は、CSSのルールを別のルールの中にネストにできる
  • ネストになったルールは通常、その親のルールの子孫要素のセレクターとして解釈される
  • 結合子を使えば、子孫要素以外のセレクターもネストできる
  • 親のセレクター自身を参照するために、ネスティングセレクター&が使える
  • CSS NestingはSassとは完全互換ではなく、&での文字列置換などは行えない

構文としてそこまで不思議に感じるものはないでしょうが、一部プリプロセッサーとの違いがあることに注意してください。

次回は、複雑なネスト、ネストと詳細度の関係、@ルールのネストについて紹介します。