CSS Nestingを試してみる

Google Chromeの実験的な機能として、CSSのネストが実装されたらしいということで、仕様を読みながら実際にその挙動を確認してみようと思います。

:::note alert
本記事で紹介する「CSS Nesting Module」の仕様は現時点（2022/11/25）で**First Public Working Draft**（2022/8/31公開）であり、今後仕様が変更される可能性があります。
:::

:::note warn
実験的な Web プラットフォームの機能
本記事の内容を確認するためには`chrome://flags/`にアクセスして、`Experimental Web Platform features`を`Enabled`にする必要があります。
:::

現状の対応ブラウザ：https://caniuse.com/css-nesting

# CSS Nesting Module

[CSS Nesting Module](https://www.w3.org/TR/css-nesting-1/)はネイティブCSSでネスト（入れ子）を導入するための仕様です。
CSSでネストを使うためには、今までSassやLess、StylusなどのCSSプリプロセッサを導入する必要がありました。CSS Nesting Moduleがブラウザに実装されると以下のようなコードをネイティブのCSSで実現できます。

```css
.parent {
    color: red;
    & > .child {
        color: bluel
    }
}

/*　以下のコードと同等に解釈される
.parent {
  color: red;
}
.parent > .child {
  color: blue
}
*/
```

# 挙動を試してみる

:::note warn
以下の挙動は[First Public Working Draft](https://www.w3.org/TR/css-nesting-1/)だけではなく[Editor’s Draft](https://drafts.csswg.org/css-nesting/)の内容も参考にしています（個人的に試した限りだと「Editor’s Draft」の内容も反映されているようでした）。
参考：https://bugs.chromium.org/p/chromium/issues/detail?id=1095675
:::

## 基本的なネスト

例えば次のようなコードがあるとします。

```html
<div class="parent">
    parent
    <div class="child">child</div>
</div>
```

```css
.parent {
  color: red;
}
.parent .child {
  color: blue
}
```

このCSSをネストで表現しようとすると、次のように書くことができます。Sassユーザーには見慣れた形ですね。

```css
.parent {
  .child {
    color: red;
  }
}
```

もしくは`&`を使って次のように書くこともできます。

```css
.parent {
  & .child {
    color: red;
  }
}
```

Chromeの`Experimental Web Platform features`を`Enable`にして確認すると、次の画像のようにスタイルが適用されることを確認できました。

![base-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/92f86bf7-7efd-aa50-52f6-8502350c6557.png)

## 要素型セレクター

要素型セレクターをネストで指定するときには`&`をつける必要があります。もしくは`:is()`使っても問題ありません。

要素型セレクターとは簡単にいうと`h1`や`p`などの要素をそのまま指定したセレクターのことです。
参考：[要素型セレクター - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/Type_selectors)

```html
<ul>
  <li>item - 01</li>
  <li>item - 02</li>
  <li>item - 03</li>
</ul>
```

```css
ul {
  & li {
    color: red;
  }
  /*　もしくは以下でもいい */
  :is(li) { 
    color: red;
  }
}

/* 以下のコードと同等
ul　li {
  color: red
}
*/

/* 以下はNG
ul {
  li {
    color: red
  }
}
*/
```

![type-selector-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/e8b31e65-66dc-fd0b-966f-cb7584a42725.png)


## 複数のネスト

複数ネストも可能です。

```html
<div class="hoge">
  hoge
  <div class="fuga">
    fuga
    <div class="piyo">piyo</div>
  </div>
</div>
```

```css
.hoge {
  color: red;
  .fuga {
    color: blue;
    .piyo {
      color: green;
    }
  }
}
/* 以下と同等
.hoge {
  color: red;
}
.hoge .fuga {
  color: blue;
}
.hoge .fuga .piyo {
  color: green;
}
*/
```

![muti-nest-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/48512a33-5eca-0ace-9f8f-60e36fdf2821.png)

## 隣接セレクタ

隣接セレクタは次のように書くことができます。

```html
<ul class="list">
  <li class="item">item - 01</li>
  <li class="item">item - 02</li>
  <li class="item">item - 03</li>
</ul>
```

```css
.item {
  + .item {
    color: blue;
  }
}
/* 以下と同等 
.item + .item {
  color: blue;
}
*/
```

![adjacent-combinator-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/1eda7a63-5117-c4ed-f28f-93db47eab8b0.png)

## 擬似クラス

次のように書くことで擬似クラスのまとめることができます（例として`:nth-child`を使用していますが、`:hover`や`:focus`などでも同様です）。

```html
<ul>
  <li>item - 01</li>
  <li>item - 02</li>
  <li>item - 03</li>
</ul>
```

```css
ul {
  & li {
    color: red;
    &:nth-child(2) {
      color: blue;
    }
    &:nth-child(3) {
      color: green;
    }
  }
}
/* 以下と同等 
ul li {
  color: red;
}
ul li:nth-child(2) {
  color: blue;
}
ul li:nth-child(3) {
  color: green;
}
*/
```

![pseudo-classes-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/ecc64dfe-135f-b5b9-0547-74305cdfb2ab.png)

## 擬似要素

擬似要素も擬似クラスと同様にまとめることができます。

```html
<div class="hoge">hoge</div>
```

```css
.hoge {
  &::before {
    content: "before - ";
  }
  &::after {
    content: "- after";
  }
}
/* 以下と同等
.hoge::before {
  content: "before - ";
}
.hoge::after {
  content: "- after";
}
*/
```

![pseudo-element-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/f7b09d48-4116-1e7d-a24c-c8f760bf2a01.png)

## 1つの要素に複数のセレクタ

1つの要素に複数のセレクタを適用したいときは`&`を使って次のように書くことができます。

```html
<div class="fuga">fuga</div>
<div class="fuga hoge">fuga and hoge</div>
```

```css
.fuga {
  color: red;
  &.hoge {
    color: blue
  }
}
/* 以下と同等
.fuga {
  color: red;
}
.fuga.hoge {
  color: blue;
}
*/
```

![compound-selector.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/d801ec05-e566-7d59-bc1f-91ecd8d8645b.png)


## `&`を連続で複数使う

あまり使うことはないかもしれませんが、`&`を連続で使うこともできます。詳細度を上げるテクニックとしては有効ですが、決して乱用するものではありません。
参考:[セレクターの複製による詳細度の向上](https://developer.mozilla.org/ja/docs/Web/CSS/Specificity#%E3%82%BB%E3%83%AC%E3%82%AF%E3%82%BF%E3%83%BC%E3%81%AE%E8%A4%87%E8%A3%BD%E3%81%AB%E3%82%88%E3%82%8B%E8%A9%B3%E7%B4%B0%E5%BA%A6%E3%81%AE%E5%90%91%E4%B8%8A)

```html
<div class="fuga">fuga</div>
```

```css
.fuga {
  color: red;
  && {
    color: blue;
  }
}
/* 以下と同等
.fuga {
  color: red;
}
.fuga.fuga {
  color: blue;
}
*/
```

![continue-ampersand.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/bedd554f-6015-801a-34f1-ce3ca7ad46ee.png)


## `:not`と`&`を使う

`:not`と`&`を使うことで、次のようなコードも可能です。

```html
<div class="hoge">hoge</div>
<div class="fuga">fuga</div>
```

```css
.hoge {
  color: red;
  :not(&) {
    color: blue;
  }
}
/* 以下と同等
  .hoge {
    color: red;
  }
  :not(.hoge) {
    color: blue;
  }
*/
```

![not-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/784238d3-4d11-6245-35e2-8c02f3ea52b8.png)


## Sassの`@at-root`の代替

次のようなコードでSassにおける`@at-root`的な使い方も可能です。

```html
<div class="hoge">hoge</div>
<div class="fuga">
  <div class="hoge">fuga > hoge</div>
</div>
```

```css
.hoge {
  color: red;
  .fuga & {
    color: blue;
  }
}
/* 以下と同等
.hoge {
  color: red;
}
.fuga .hoge {
  color: blue;
}
*/
```

![at-root-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/99b9f148-8372-b6c8-2913-dfbae0c89ca3.png)

## メディアクエリー

次のように書くことで`@media`のネストも可能です。

```html
 <div class="hoge">hoge</div>
```

```css
.hoge {
  color: red;
  @media (min-width: 900px) {
    color: blue;
  }
}
/* 以下と同等
.hoge {
  color: red;
}
@media (min-width: 900px) {
  .hoge {
    color: blue;
  }
}
*/
```

また`@media`の中に`@media`をネストすることもできます。

```css
.hoge {
  color: red;
  @media (min-width: 900px) {
    color: blue;
    @media (max-width: 1200px) {
      color: green;
    }
  }
}
/* 以下と同等
.hoge {
  color: red;
}
@media (min-width: 900px) {
  .hoge {
    color: blue;
  }
}
@media (min-width: 900px) and (max-width: 1200px) {
  .hoge {
    color: green;
  }
}
*/
```

## 詳細度に関して

通常、ネストを使用すると詳細度を高めることにつながります。

つまり次のコードでは`.fuga`に`color:blue`が適用されることになります。

```html
<div class="hoge">
  <div class="fuga">fuga</div>
</div>
```

```css
.hoge {
  .fuga {
    color: blue;
  }
}
.fuga {
  color: red;
}
/* 以下と同等になるので.fugaは`color:blue`が適用される
.hoge .fuga {
  color: blue;
}
.fuga {
  color: red;
}
*/
```

![nest-specificity.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/e10a97d2-1f44-f623-9a2e-a3a8b4d7ecf1.png)

`:where()`の擬似クラス関数を使うことで、これを回避することができます。

```css
.hoge {
  :where(&) .fuga {
    color: blue;
  }
}
.fuga {
  color: red;
}
/* 以下と同等になるので.fugaは`color:red`が適用される
:where(.hoge) .fuga {
  color: blue;
}
.fuga {
  color: red;
}
*/
```

![nest-specificity-where.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/3cc979d2-d573-262a-77d6-06dacb2d9cf4.png)

`:where()`を使うと詳細度を0に保つことができます。詳しくは以下をご参照ください。
[:where() - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/:where)

## ネストと優先順位

```css
.hoge {
  & {
    color: red;
  }
}
.hoge {
  color: blue;
}
```

上記のコードを書いたときに`&{}`は詳細度を高めないので、後ろに書いた`color:blue`が優先されます。
感覚的にもこれは問題ないように思えますね。

しかし、次ようなコードを書いたときには注意が必要です。

```html
<div class="hoge">hoge</div>
```

```css
.hoge {
  color: red;
  & {
    color: blue;
  }
  color: green;
}

/* 以下と同等 `&{}`が優先される
.hoge {
  color: red;
  color: green;
  color: blue;
}
*/
```

![nest-order-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/92c83b43-b83a-6a74-2721-2f7a71822b56.png)

通常の宣言とネストされた宣言を混合して書くと、ネストされた宣言が優先されることに注意しましょう。またこの時、詳細度は変わりません。

## セレクタの一部を`&`で繋げる

BEMで見かける記法ですねですね。`block__element--modifier`の`element`や`modifier`を`&`で繋げていく記法になります。これは**できません**のでご注意ください。

```html
<div class="block">
  block
  <div class="block__element">block__element</div>
</div>
```

```css
.block {
  color: red;
  /* 以下はSassと同等の挙動ではない */
  &__element {
    color: blue;
  }
}
/* Sassでは以下となる
.block {
  color: red;
}
.block__element {
  color: blue;
}
*/
/* CSS Nesting Moduleでは以下のように解釈されるらしい
.block {
  color: red;
}
__element.block {
  color: blue;
}
*/
```

![slector-ampersand-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/93420e56-542d-b817-6c2c-190b5118b8bd.png)


# おわりに

CSS Nesting Moduleの仕様は現時点では**First Public Working Draft**であり、今後仕様が変更される可能性は十分にあります。**Editor’s Draft**を読んでみても、まだまだ不安定な箇所があり、この段階で一喜一憂するものではないでしょう。
とはいえ、CSS Nesting Mdduleが導入されることでコードがクリーンになったり、楽になる部分もあると思いますので、各々のブラウザでサポートされるのが待ち遠しいですね。

また、PostCSSのプラグインを使うことで、現時点でもCSS Nesting Moduleの機能を導入することが可能です。興味がある方は以下のリンクをご参照ください。
[PostCSS Nesting](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting)

# 参考

- https://www.w3.org/TR/css-nesting-1/
- https://zenn.dev/moneyforward/articles/css-nesting-without-sass
- https://qiita.com/otsuky/items/68a5fa533aff3f9386e5