そろそろ Native CSS Nesting の話をしよう

この記事では 2021年8月に公開された [CSS Nesting Module](https://www.w3.org/TR/2021/WD-css-nesting-1-20210831/) の First Public Working Draft について解説します。

# 3 行でわかる CSS Nesting Module

- 今年 8/31 に CSS Nesting Module の [First Public Working Draft](https://www.w3.org/TR/2021/WD-css-nesting-1-20210831/) が公開
- スタイルルールをネストして記述することができ、CSS の可読性と保守性が向上させるのに役立つ
- 対応ブラウザはまだないが、これから出てきそう！

# CSS Nesting Module とは
CSS Nesting Module とは、スタイルルールを入れ子（ネスト）にして記述するための仕様です。
今年 8/31 に CSS Nesting Module の [First Public Working Draft](https://www.w3.org/TR/2021/WD-css-nesting-1-20210831/) が公開され話題になりました（ちなみに [Editor's Draft](https://github.com/w3c/csswg-drafts/pull/2878) は 2018 年）。
スタイルをネストする記法は古来より Sass や Less などでもお馴染みですが、重複する指定を減らして可読性を高めたり、関連のスタイルをグルーピングすることで保守性を高めたりできるメリットがあります。
ネイティブの CSS でスタイルのネストがサポートされることによって、プリプロセッサを使用せずともこの恩恵を享受できるようになります。

## 例

```css
.header {
  background-color: blue;
}
.header p {
  font-size: 16px;
}
.header p a:hover {
  color: green;
}
```
上記のコードを SCSS や Less などのプリプロセッサを使わずに、このように書くことができます！[^1]

[^1]: コードハイライトでエラーになっているように見えますが、CSS Nesting Module に時代がまだ追いついていないだけで間違っているというわけではありません

```css
.header {
  background-color: blue;
  & p {
    font-size: 16px;
    & a {
      &:hover {
        color: green;
      }
    }
  }
}
```
すっげー！🤩


# 記述方法は 2 種類
ネストしたスタイルの書き方には下記の 2 つがあります。

- Direct Nesting
- `@nest`


## Direct Nesting

ネスティングセレクタ `&` 使う構文で、各プリプロセッサでもお馴染みの記法です。

### 例

```css
p {
  color: black;
  .body {
    color: red; // → p.body に適用される
  }
}
```

↓ ネストの中で隣接セレクタも使用可能

```css
.foo {
  color: blue;

  & + .bar {
    color: red; // → .foo + .bar に適用される
  }
}
```

↓ メディアクエリもネストできる

```css
.header {
  font-size: 24px;

  @media (max-width: 760px) {
    & {
      font-size: 12px;
    }
  }
}
```

その他にも [Working Draft](https://www.w3.org/TR/2021/WD-css-nesting-1-20210831/#direct) に豊富な具体例があるので、興味がある方は是非ご覧ください。

### Direct Nesting の注意点
Direct Nesting では `&` は常にセレクタの最初に書かなければないというルールがあります。
つまり、下記のようにセレクタの後ろに `&` を書いてもうまく解釈されません。

```css
.header {
  background: white;
  .dark & { //← .dark .header に適用されない🤯
    background: blue;
  }
}
```
このような場合に役に立つのが、次に紹介する `@nest` を使う構文です。 


## `@nest`

`@nest` を使うことで `&` を書く位置をもっと柔軟に決めることができます。

### 例

```css
.header {
  background: white;
  @nest .dark & { //← .dark .header に適用される
    background: blue;
  }
}
```

↓ 擬似クラスにも`&`を使用することができます

```css
.foo {
  color: red;
  @nest :not(&) { //← :not(.foo) に適用される
    color: blue;
  }
}
```


## Direct Nesting と `@nest` は一緒に使える
Direct Nesting と `@nest` を一緒に使うことで、より複雑なネストの指定が可能になります。

### 例
```css
.foo {
  color: blue;
  @nest .bar & {
    color: red;
    &.baz {
      color: green;
    }
  }
}
```

## なぜ `@nest` が必要なの？

最初から Direct Nesting でだけで書けるようにすればいいじゃない！と思う人もいるかもしれません（私は思いました）。

```css
.header {
  background-color: white;
  .dark & {
    background-color: blue;
  }
}
```

上記のような書き方を許容してしまうと、テキストが宣言なのかスタイルルールの始まりなのかを判断するために無限に先読みが必要になってしまいます。
例えば、`color:hover...` という記述をパーサーは `color` プロパティなのか、`<color>` 要素なのかその箇所だけで判断することができません。
そのため、`&` もしくは `@nest` でセレクタを開始することで、「これはセレクタである」ということをパーサーに伝える必要があるのです。
参考: https://www.w3.org/TR/2021/WD-css-nesting-1-20210831/#:~:text=Why%20can%E2%80%99t%20everything%20be%20directly%20nested%3F

# CSS Nesting Module でできないこと

Sass や Lessでは下記のように `&` を使用して文字列を連結し、BEM のクラス名をセレクタとして楽々指定することができました。

```css
.foo {
  color: blue;
  &__bar {
    color: red;
  }
}
```

しかし、この書き方でクラス名同士を連結することは、セレクタ構文の仕様に矛盾しているため、`__bar` は HTML のカスタムエレメントだと解釈されてしまいます。
# CSS Nesting Module の注意点

CSS Nesting ではセレクタが解決されると、ブラウザは `:is()`[^2] で親セレクタをラップしたセレクタに置き換えて解釈します。


```css
.foo,
.bar {
  color: blue;
  & + .bar {
    color: red;
  }
}
```

↓

```css
.foo,
.bar {
  color: blue;
}
:is(.foo, .bar) + .baz {
  color: red;
}
```

`:is()`と類似の擬似セレクタに `:where()`[^3]というものもありますが、 `:is()`だと解釈されるのがポイントです。
[^2]: :where(): https://developer.mozilla.org/ja/docs/Web/CSS/:where
[^3]: :is(): https://developer.mozilla.org/ja/docs/Web/CSS/:is


`:is()` を使うと `:where()` とは違いセレクタ全体の詳細度にカウントされるため、下記の様にネストせずに書いた場合と同じ詳細度になります。

```css
.foo + .baz, .bar + .baz  {
  color: red;
}
```


つまり、ネストすればするほど詳細度は高くなってしまうということです。
スタイルの上書きに `！important` を使わざるをえない地獄を味あわないためにも、ネストの使いすぎに注意したいですね。


# CSS Nesting Module を使う

ブラウザ自体は未対応ですが、PostCSS を使用することで今からでも Nesting Module を使用できます。

- [postcss-nesting](https://github.com/csstools/postcss-nesting)
    - GoogleChromeLabs による https://designcember.com/ というサイトでも使用されており、実際のコードを見ることができる
        - https://github.com/GoogleChromeLabs/designcember
- [postcss-preset-env](https://preset-env.cssdb.org/)
  - Nesting Module 以外の CSS の新しい仕様も使えるように polyfill を適応してくれる
  - [playground](https://preset-env.cssdb.org/playground) があるのでサクッと試したい場合におすすめ

# おわりに

これまで見てきた CSS Nesting Module の仕様はまだ Working Draft であるため、今後変更の加わる可能性が大いにあります。
これから順次ブラウザにサポートされることが予想されるため、今後の動向に期待が高まりますね。
しかし、新しい仕様が登場してブラウザがそれをサポートしても、古いブラウザのシェアが高い状態だとなかなか実務では取り入れづらく、もどかしい思いをしている人も多いのではないでしょうか。
全人類が最新ブラウザにアップデートするように共に聖夜に祈りを捧げましょう🙏



# 参考

- [CSS Nesting Module](https://www.w3.org/TR/2021/WD-css-nesting-1-20210831/)
- [CSS Nesting, specificity and you | Kilian Valkh](https://kilianvalkhof.com/2021/css-html/css-nesting-specificity-and-you/)
- [Native CSS nesting: What you need to know - LogRocket Blog](https://blog.logrocket.com/native-css-nesting/)
- [CSS Nesting, specificity, and you | CSS-Tricks](https://css-tricks.com/css-nesting-specificity-and-you/)