- CSS
CSSネストの書き方|&の使い方とSassとの違い・対応ブラウザ
CSSネストとは、あるスタイルルールの波かっこの中に、別のルールを入れ子で書ける CSS の標準機能です。 Sass などのコンパイルなしで、ブラウザがそのまま読み取ります。.card { .title { ... } } と書けば .card .title に、.card { &:hover { ... } } と書けば .card:hover に当たります。
書き方は Sass とよく似ていますが、同じコードでも結果が変わる箇所が3つあります。&__elem のような文字列の連結ができないこと、詳細度が :is() と同じ計算になること、そしてネストの後ろに書いた宣言の順序です。この記事では、それぞれを Chromium 141 で実際に読み込ませ、適用された色や CSSOM(ブラウザが解釈したルールの構造)を出力して確かめました。対応ブラウザのバージョン表、Sass の BEM 記法からの書き換え表、コピペ用のパターン集も載せています。
CSSネストの早見表
書き方と展開結果
ネストの中の書き方(親は .card) |
展開結果(同じ意味の書き方) | & の扱い |
|---|---|---|
.title { } |
.card .title |
省略できる |
& .title { } |
.card .title |
書いても同じ |
a { } |
.card a |
省略できる(要素名で始めてもよい) |
> li { } |
.card > li |
省略できる |
+ p { } |
.card + p |
省略できる |
&.active { } |
.card.active |
必要 |
&:hover { } |
.card:hover |
必要 |
&::before { } |
.card::before |
必要 |
&:not(.on) { } |
.card:not(.on) |
必要 |
.dark & { } |
.dark .card |
必要(親を後ろに置く) |
& + & { } |
.card + .card |
必要 |
&__title { } |
(無効。読み飛ばされる) | 書けない |
&-wide { } |
(無効。読み飛ばされる) | 書けない |
対応ブラウザ(要点)
| ブラウザ | & が必須だった時期 | 要素名で始められる版(緩和後) |
|---|---|---|
| Chrome / Edge | 112〜119 | 120 以降 |
| Firefox | (なし) | 117 以降 |
| Safari(macOS / iOS) | 16.5〜17.1 | 17.2 以降 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「CSSネストの書き方|&の使い方とSassとの違い・対応ブラウザ」
検証した環境
| 項目 | 版 |
|---|---|
| ブラウザ | Chromium 141.0.7390.37(Playwright 1.56.0 から起動) |
| Sass | Dart Sass 1.105.1(npm の sass パッケージ) |
| 対応ブラウザの版 | npm の @mdn/browser-compat-data 8.1.4(2026-10-01 付)から取得 |
適用結果は getComputedStyle() で取り出した値、ルールの構造は document.styleSheets の cssRules をたどって出力したものです。Firefox と Safari での実行は行っていません(未実行)。この2つのブラウザについては、対応する版を上記のデータで確認しています。
基本の書き方
ネストなしの CSS と、ネストした CSS を並べます。
/* ネストなし */
.card { padding: 16px; }
.card .title { font-weight: 700; }
.card:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.12); }
/* ネストあり(同じ意味) */
.card {
padding: 16px;
.title { font-weight: 700; }
&:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.12); }
}
親のセレクタを何度も書かずに済み、関連するルールがひとかたまりになるのが利点です。& は「親のセレクタそのもの」を表す記号で、ネストセレクタと呼ばれます。
&が必要なケース・省略できるケース
判断の基準は1つです。親との間に「空白(子孫)」やコンビネータ(>、+、~)が入るなら省略でき、親にぴったりくっつけたいなら & が必要です。
検証コード
次の HTML と CSS を読み込み、各要素の color を取り出しました。色の値(rgb(1,1,1) など)は、どのルールが当たったかを見分けるための目印です。
<div class="card">A
<p class="title">B</p>
<a>C</a>
<span class="active">D</span>
</div>
<p>E</p>
<div class="card active">F</div>
<div class="dark"><div class="card">G</div></div>
<p class="title">H</p>
.card {
color: rgb(1,1,1);
.title { color: rgb(2,2,2); }
a { color: rgb(3,3,3); }
.active { color: rgb(40,40,40); }
&.active { color: rgb(4,4,4); }
+ p { color: rgb(5,5,5); }
.dark & { color: rgb(6,6,6); }
}
実行結果です。
A div.card => rgb(1, 1, 1)
B .card の中の p.title => rgb(2, 2, 2)
C .card の中の a => rgb(3, 3, 3)
D .card の中の span.active => rgb(40, 40, 40)
E .card の直後の p => rgb(5, 5, 5)
F div.card.active => rgb(4, 4, 4)
G .dark の中の div.card => rgb(6, 6, 6)
H .card の外の p.title => rgb(0, 0, 0)
D と F を見比べてください。.active { } は「.card の中にある .active」(D)に、&.active { } は「.card かつ .active」(F)に当たりました。 & の有無で対象がまったく別の要素になります。
ブラウザは & を自動で補っている
同じ形の CSS を読み込ませて、ブラウザが解釈したルールを出力すると次のようになります。
CSSStyleRule | .card | color: red;
CSSStyleRule | & .title | color: blue;
CSSStyleRule | &.active | color: green;
CSSStyleRule | & + p | color: gray;
CSSStyleRule | .dark & | color: navy;
.title と書いたルールは & .title に、+ p と書いたルールは & + p になっています。& を書かなかった場合、ブラウザが先頭に「& と空白」を補うということです。すでにどこかに & が書かれている .dark & には、何も補われていません。
| 書き方 | ブラウザの解釈 | 意味 |
|---|---|---|
.title { } |
& .title |
親の子孫の .title |
+ p { } |
& + p |
親の直後の p |
&.active { } |
&.active |
親かつ .active |
:hover { } |
& :hover |
親の子孫でホバー中の要素(親自身ではない) |
.dark & { } |
.dark & |
.dark の中にある親 |
4行目がつまずきやすい箇所です(.card { :hover { ... } } を読み込ませると、解釈結果は & :hover でした)。:hover { } と & なしで書くと「親の中にある、ホバー中のすべての要素」になります。疑似クラス・疑似要素・属性セレクタ・クラスの重ねがけは、必ず & をくっつけて書くと覚えておくと迷いません。
入れ子を重ねたとき・セレクタリストのとき
.list {
li {
color: rgb(1,1,1);
&.on { color: rgb(2,2,2); }
&:not(.on) { font-weight: 700; }
&::before { content: "- "; }
}
}
h2, h3 { &.h { color: rgb(5,5,5); } }
.h { h2& { font-style: italic; } }
li => rgb(1, 1, 1)
li.on => rgb(2, 2, 2)
li font-weight: 700
li::before content: "- "
h2.h => rgb(5, 5, 5)
h3.h => rgb(5, 5, 5)
h2 font-style: italic / h3: normal
2段目の & は、1つ外側のルール全体(.list li)を指します。親が h2, h3 のようなセレクタリストなら、&.h は h2.h と h3.h の両方に当たります。最後の h2& は「h2 要素かつ .h」という意味で、h2 だけが斜体になりました。要素名と組み合わせるときは、要素名を先に、& を後ろに書きます。
書けないケース:&__elemの連結
Sass では &__elem と書くと、親のクラス名に文字列をつなげて .block__elem を作れます。CSSネストの & は文字列ではなく「親セレクタに当たる要素」を表すので、連結はできません。
.block {
color: rgb(1,1,1);
&__elem { color: rgb(9,9,9); }
}
<div class="block">
<div class="block__elem">elem</div>
</div>
CSSStyleRule | .block | color: rgb(1, 1, 1);
block: rgb(1, 1, 1)
block__elem: rgb(1, 1, 1)
解釈されたルールは .block の1つだけで、&__elem のルールは丸ごと読み飛ばされました。.block__elem の文字色は、親から継承した rgb(1, 1, 1) のままです。エラーは表示されず、ほかのルールはそのまま有効なので、気づきにくい点に注意します。
同じ形のものをまとめて試した結果です。
| 書き方 | Chromium 141 での結果 |
|---|---|
&__elem { } |
読み飛ばされる |
&-wide { } |
読み飛ばされる |
&_x { } |
読み飛ばされる |
&1 { } |
読み飛ばされる |
&div { } |
読み飛ばされる |
div& { } |
有効(div 要素かつ親) |
.block__elem { } |
有効(& .block__elem と解釈) |
無効なルールが1つあっても、同じ親の中のほかのルールには影響しません。.list { & %bad { ... } & .ok { ... } } と書いて確かめたところ、& .ok のルールは残っていました。
同じコードを Sass でコンパイルすると
.block {
color: black;
&__elem { color: gray; }
&--wide { width: 100%; }
}
/* Dart Sass 1.105.1 の出力 */
.block {
color: black;
}
.block__elem {
color: gray;
}
.block--wide {
width: 100%;
}
Sass はコンパイルの時点で文字列をつなげ、平らな CSS を出力します。Sass から拡張子だけ変えて移すと、BEM の &__ と &-- の部分がすべて効かなくなるので、次の表のように書き換えます。
SassのBEM記法からの書き換え対応表
| Sass での書き方 | CSSネストでの書き方 | 当たる対象 |
|---|---|---|
&__title { } |
.card__title { } |
.card の中の .card__title |
&--wide { } |
&.card--wide { } |
.card かつ .card--wide |
&__title--sub { } |
.card__title--sub { } |
.card の中の .card__title--sub |
&:hover { } |
&:hover { } |
そのまま使える |
&.is-open { } |
&.is-open { } |
そのまま使える |
.dark & { } |
.dark & { } |
そのまま使える |
& + & { } |
& + & { } |
そのまま使える |
& > li { } |
> li { } または & > li { } |
そのまま使える |
#{&}__title { } |
.card__title { } |
補間は使えないのでクラス名を書く |
@at-root .x { } |
ネストの外に書く | ネストから抜ける指定はない |
書き換え後のコードを実際に読み込ませました。
.card {
color: rgb(1,1,1);
.card__title { color: rgb(2,2,2); }
&.card--wide { color: rgb(3,3,3); }
.card__title--sub { color: rgb(4,4,4); }
}
card--wide => rgb(3, 3, 3)
card__title => rgb(2, 2, 2)
card__title--sub => rgb(4, 4, 4)
3つとも意図どおりに当たっています。なお、Sass の &__title は .card__title 単体(詳細度 0,1,0)になりますが、書き換え後の .card .card__title は詳細度が 0,2,0 に上がります。詳細度を上げたくないときは、エレメントのルールをネストの外に平らに書くのも良い選択です。
詳細度は:is()相当になる
2つ目の違いは詳細度です。& の詳細度は、親のセレクタリストの中で最も高いものが採用されます。 これは :is() と同じ計算方法です。
Sassと同じコードで結果が変わる例
#main, .card {
.title { color: rgb(255,0,0); } /* 赤 */
}
.card .title { color: rgb(0,0,255); } /* 青 */
<div class="card">
<p class="title">タイトル</p>
</div>
HTML に #main は登場しません。直感的には、後から書いた青が勝ちそうに見えます。結果は次のとおりです。
| 書き方 | .card の中の .title の色 | 理由 |
|---|---|---|
| CSSネスト(上のコード) | rgb(255, 0, 0)(赤) | :is(#main, .card) .title と同じで詳細度 1,1,0 |
| Sass でコンパイルした CSS | rgb(0, 0, 255)(青) | #main .title, .card .title に展開され、.card 側は 0,2,0 |
:is(#main, .card) .title と書いた CSS |
rgb(255, 0, 0)(赤) | CSSネストと同じ結果 |
Sass の出力は次のとおりで、セレクタが1つずつ展開されています。
/* Dart Sass 1.105.1 の出力 */
#main .title, .card .title {
color: red;
}
.card .title {
color: blue;
}
Sass では「当たったセレクタ(.card .title)」の詳細度 0,2,0 どうしの比較になり、後に書いた青が勝ちます。CSSネストでは、実際には当たっていない #main の詳細度まで含めて計算されるため、1,1,0 の赤が勝ちます。
詳細度を上げたくないときは:where()
親を :where() で包むと、その部分の詳細度が 0 になります。
:where(#main, .card) {
.title { color: rgb(255,0,0); }
}
.card .title { color: rgb(0,0,255); }
title => rgb(0, 0, 255)
青が勝つようになりました。親のセレクタリストに ID と クラスを混ぜない、混ぜる必要があるときは :where() で包む、という2点で、Sass から移したときの差を小さくできます。
ネストの後ろに書いた宣言の扱い
3つ目の違いは、入れ子のルールより後ろに書いた宣言です。
.box {
color: rgb(255,0,0); /* 赤 */
& { color: rgb(0,0,255); } /* 青 */
color: rgb(0,128,0); /* 緑:ネストの後ろ */
}
& { } は親と同じ要素・同じ詳細度のルールです。Chromium 141 での結果は次のとおりです。
CSSStyleRule | .box | color: rgb(255, 0, 0);
CSSStyleRule | & | color: rgb(0, 0, 255);
CSSNestedDeclarations | | color: rgb(0, 128, 0);
computed: rgb(0, 128, 0)
最後に書いた緑が適用されました。 ネストの後ろの宣言は CSSNestedDeclarations という独立したかたまりとして、書いた位置のまま扱われています。書いた順番どおりに後勝ちになるので、見た目と結果が一致します。
CSSNestedDeclarations に対応する前の版では、後ろに書いた宣言も先頭の宣言とまとめて扱われ、上の例は青になるとされています(以前の版での実行は今回行っていません。未実行)。対応した版は次のとおりです。
| ブラウザ | 書いた順のまま扱う版(CSSNestedDeclarations 対応) |
|---|---|
| Chrome / Edge | 130 以降 |
| Firefox | 132 以降 |
| Safari(macOS / iOS) | 18.2 以降 |
@media を挟んだ場合も同じ動きです。
.box {
color: rgb(255,0,0);
@media (min-width: 1px) { color: rgb(0,0,255); }
color: rgb(0,128,0);
}
CSSStyleRule | .box | color: rgb(255, 0, 0);
CSSMediaRule | (min-width: 1px) |
CSSNestedDeclarations | | color: rgb(0, 0, 255);
CSSNestedDeclarations | | color: rgb(0, 128, 0);
computed: rgb(0, 128, 0)
メディアクエリの条件は満たしていますが、後ろに書いた緑が勝ちました。Dart Sass 1.105.1 も同じ順序を保つ出力になっています。
/* .box { color: red; & { color: blue; } color: green; } の Sass 出力 */
.box {
color: red;
}
.box {
color: blue;
}
.box {
color: green;
}
ブラウザの版によって結果が分かれる書き方なので、宣言はルールの先頭にまとめ、入れ子のルールや @media はその後ろに書くという並べ方にしておくと、どの版でも同じ結果になります。
/* おすすめの並べ方 */
.box {
color: green; /* 1. 宣言を先に */
padding: 16px;
&:hover { color: blue; } /* 2. 入れ子のルール */
@media (min-width: 768px) { /* 3. アットルール */
padding: 24px;
}
}
@media・@container・@layer・@scopeのネスト
アットルールもスタイルルールの中に入れられます。アットルールの中に直接書いた宣言は、親のセレクタに対して適用されます。
@layer base, comp;
.wrap { container-type: inline-size; }
.card {
color: rgb(1,1,1);
@media (min-width: 600px) {
color: rgb(2,2,2);
.title { color: rgb(20,20,20); }
}
@container (min-width: 400px) {
.title { text-decoration-line: underline; }
}
@supports (display: grid) {
display: grid;
}
@layer comp { padding-top: 8px; }
@layer base { padding-top: 4px; }
@scope (&) to (.title) {
:scope { outline-style: dashed; }
p { outline-style: dotted; }
}
}
<div class="wrap" style="width:500px">
<div class="card">
<p class="title">タイトル</p>
</div>
</div>
画面幅 800px で読み込んだ結果です。
.card color: rgb(2, 2, 2)
.title color: rgb(20, 20, 20)
.title text-decoration-line: underline
.card display: grid
.card padding-top: 8px
.card outline-style: dashed
.title outline-style: none
| アットルール | ネストしたときの動き | 今回の結果 |
|---|---|---|
@media |
条件を満たすとき、中の宣言が親に当たる。さらにルールも入れられる | .card と .title の色が変わった |
@container |
祖先のコンテナの幅で切り替わる | 幅 500px のコンテナで下線が付いた |
@supports |
機能に対応しているときだけ当たる | display: grid が当たった |
@layer |
宣言をそのレイヤーに入れる。順序は先頭の @layer base, comp; で決まる |
後のレイヤー comp の 8px が勝った |
@scope |
(&) で親を起点にし、to (...) で範囲の終わりを指定できる |
.card に dashed。範囲の境界にした .title には当たらない |
@media の中を CSSOM で見ると、直接書いた宣言は CSSNestedDeclarations、ルールは & .title として保持されていました。
CSSStyleRule | .card | color: red;
CSSMediaRule | (min-width: 600px) |
CSSNestedDeclarations | | color: blue;
CSSStyleRule | & .title | color: navy;
コンポーネントのスタイルとレスポンシブ対応を1か所にまとめられるのが、アットルールをネストする利点です。
対応ブラウザのバージョン表
npm の @mdn/browser-compat-data 8.1.4(2026-10-01 付)から取り出した値です。
| 機能 | Chrome | Edge | Firefox | Safari | iOS Safari |
|---|---|---|---|---|---|
| CSSネスト(& が必須。要素名で始められない) | 112〜119 | 112〜119 | - | 16.5〜17.1 | 16.5〜17.1 |
| CSSネスト(緩和後。要素名で始められる) | 120 | 120 | 117 | 17.2 | 17.2 |
| ネスト後の宣言を書いた順に扱う(CSSNestedDeclarations) | 130 | 130 | 132 | 18.2 | 18.2 |
@container |
105 | 105 | 110 | 16 | 16 |
@layer |
99 | 99 | 97 | 15.4 | 15.4 |
@scope |
118 | 118 | 146 | 17.4 | 17.4 |
@supports selector() |
83 | 83 | 69 | 14.1 | 14.5 |
:is() / :where() |
88 | 88 | 78 | 14 | 14 |
- & 必須期(Chrome 112〜119、Safari 16.5〜17.1)では、
.card { a { ... } }のように要素名で始まる入れ子が読み取られません。この時期の版も対象に含めるなら、& a { ... }と&を付けて書きます。 - 緩和後(Chrome 120、Firefox 117、Safari 17.2 以降)は、
&なしで要素名から始められます。Firefox は最初の対応版から緩和後の書き方に対応しています。 @scopeは、データ上 Safari 26.0〜26.3 に「@scope 内のルールが input と textarea に当たらない」という注記があり、26.4 で解消とされています。
対象にする版の目安をまとめます。
| 対象にしたい環境 | 書き方の方針 |
|---|---|
| 2023年12月以降に更新された主要ブラウザ | そのまま使える。& の省略も可 |
| Chrome 112〜119 / Safari 16.5〜17.1 も含める | 要素名で始めるときは必ず & を付ける |
| それより前の版も含める | Sass などで平らな CSS に変換して配信する |
@supportsによるフォールバック
ネストに対応していないブラウザでは、入れ子のルールが読み飛ばされます。土台のスタイルを平らな CSS で先に書き、ネストに対応した環境だけ上書きする形にしておくと、どちらでも表示が整います。判定には @supports selector(&) を使います。
/* 1. どのブラウザでも当たる土台 */
.card .title { color: rgb(1,1,1); }
/* 2. ネストに対応しているときだけ */
@supports selector(&) {
.card {
.title { color: rgb(2,2,2); }
}
}
/* 3. ネストに対応していないときだけ */
@supports not selector(&) {
.card .title { color: rgb(3,3,3); }
}
title => rgb(2, 2, 2)
CSS.supports("selector(&)"): true
Chromium 141 では2番のルールが当たり、JavaScript の CSS.supports("selector(&)") も true を返しました。非対応ブラウザでの動作(3番が当たること)は、今回の環境では未実行です。
広い範囲のブラウザに同じ見た目を届けたいときは、ビルドの段階で平らな CSS に変換する方法が確実です。
| 方法 | 内容 | 今回の確認 |
|---|---|---|
@supports selector(&) |
対応環境だけネスト版を当てる | 実行済み(Chromium 141) |
| Sass でコンパイル | .scss で書き、平らな CSS を配信する | 実行済み(Dart Sass 1.105.1) |
| PostCSS や Lightning CSS などの変換ツール | CSSネストの書き方のまま、平らな CSS に変換する | 未実行 |
コピペ用パターン集
次のコードは、まとめて Chromium 141 に読み込ませ、31個のルール(アットルールを含む)がすべて解釈されること(読み飛ばしがないこと)を確かめたものです。
カード(子要素・状態・レスポンシブ)
.card {
padding: 16px;
border: 1px solid #d1d5db;
border-radius: 8px;
.card-title { font-size: 1.25rem; font-weight: 700; }
.card-text { color: #4b5563; }
&.is-featured { border-color: #1d4ed8; }
&:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.12); }
&:has(img) { padding-block-start: 0; }
@media (min-width: 768px) {
padding: 24px;
.card-title { font-size: 1.5rem; }
}
}
ナビゲーション(リンクの状態・現在のページ)
.nav {
display: flex;
gap: 16px;
a {
color: inherit;
text-decoration: none;
&:hover, &:focus-visible { text-decoration: underline; }
&[aria-current="page"] { font-weight: 700; }
}
> li + li { border-inline-start: 1px solid #d1d5db; }
}
ボタン(状態・疑似要素・隣り合うボタン)
.btn {
padding: 8px 20px;
color: #ffffff;
background: #1d4ed8;
&:hover { background: #1e40af; }
&:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
&:disabled { color: #4b5563; background: #e5e7eb; }
&::after { content: " →"; }
& + & { margin-inline-start: 8px; }
}
& + & は .btn + .btn の意味です。ボタンを2つ並べたところ、2つ目だけに 8px の余白が付きました。
親の状態で切り替える(& を後ろに置く)
.menu {
display: none;
.is-open & { display: block; }
:root[data-theme="dark"] & { background: #111827; }
@media (prefers-reduced-motion: no-preference) { transition: opacity 0.2s; }
}
.is-open の中に .menu を置いて確かめると、display は block になりました。「祖先がこの状態のとき」という条件を、対象のルールの中に書けるのがこの形の便利なところです。
リスト(:not・:nth-child)
.list {
li {
padding-block: 8px;
&:not(:last-child) { border-block-end: 1px solid #e5e7eb; }
&:nth-child(odd) { background: #f9fafb; }
}
}
コンテナクエリ
.panel-wrap { container-type: inline-size; }
.panel {
display: grid;
gap: 12px;
@container (min-width: 480px) {
grid-template-columns: 1fr 2fr;
.panel-side { order: -1; }
}
}
SassとCSSネストの違いまとめ
| 項目 | Sass(SCSS) | CSSネスト |
|---|---|---|
| 処理される場所 | コンパイル時(平らな CSS を出力) | ブラウザが直接読む |
&__elem などの連結 |
できる | できない(ルールごと読み飛ばされる) |
| 親がセレクタリストのときの詳細度 | セレクタごとに展開され、個別に計算 | :is() と同じで、最も高いものを採用 |
| ネストの後ろの宣言 | 書いた順を保って出力(1.105.1 で確認) | 書いた順のまま扱う(Chrome 130・Firefox 132・Safari 18.2 以降) |
& の省略 |
子孫・コンビネータで省略可 | 同じ |
| 要素名と親の組み合わせ | div& はエラー("&" may only used at the beginning of a compound selector.) |
div& と書ける |
変数・mixin・関数・@extend |
使える | ネストの機能には含まれない(変数はカスタムプロパティで代替) |
@at-root・#{&} の補間 |
使える | 使えない |
| 配信されるファイルサイズ | 展開後なので親セレクタが繰り返される | 入れ子のままなので繰り返しが減る |
Sass の変数や mixin を使っていないなら、CSSネストだけでコンパイルなしの構成にできます。 Sass を使い続ける場合も、Sass は入れ子を平らに展開して出力するので、そのまま広い範囲のブラウザで動きます。
つまずきやすい点と対処
| 起きること | 理由 | 対処 |
|---|---|---|
&__elem のスタイルが当たらない |
連結はできず、ルールが読み飛ばされる | .block__elem { } とクラス名を書く |
:hover { } で子要素までホバーに反応する |
& :hover(子孫)と解釈される |
&:hover { } とくっつける |
.active { } が親自身に当たらない |
& .active(子孫)と解釈される |
&.active { } と書く |
| 後から書いたルールで上書きできない | 親のリストに ID があり、詳細度が :is() 相当で高い |
親を :where() で包む、ID とクラスを混ぜない |
| ブラウザによって色が変わる | ネストの後ろに宣言を書いている | 宣言を先頭にまとめる |
一部の版で a { } の入れ子だけ効かない |
& 必須期の版(Chrome 112〜119、Safari 16.5〜17.1) | & a { } と書く |
| ネストが深くなりセレクタが長い | 段を重ねるたびに詳細度が上がる | 2〜3段までを目安にする |
| 疑似要素のルールの中に書いた入れ子が当たらない | & は疑似要素を表せない |
疑似要素は一番内側に &::before { } と書く |
最後の行は、.tip::before { color: rgb(1,1,1); & { color: rgb(2,2,2); } } で確かめました。入れ子の & { } はルールとしては残りますが、::before の色は rgb(1, 1, 1) のままで、入れ子の側は当たりませんでした。.tip2 { &::before { ... } } のように、疑似要素を一番内側に置く書き方は意図どおりに当たります。
エラーが表示されないまま読み飛ばされることが多いので、当たらないときは開発者ツールのスタイル欄で、そのルールが一覧に出ているかを確かめるのが近道です。
よくある質問
Q. & は必ず書く必要がありますか?
A. 子孫セレクタ(.title、a)やコンビネータ(> li、+ p)で始めるときは省略できます。疑似クラス(&:hover)、疑似要素(&::before)、クラスの重ねがけ(&.active)、親を後ろに置く形(.dark &)では必要です。Chrome 112〜119 と Safari 16.5〜17.1 も対象に含めるときは、要素名で始める場合にも & を付けます。
Q. Sass の &__title が効かないのはなぜですか?
A. CSSネストの & は文字列ではなく、親セレクタに当たる要素を表すためです。Chromium 141 で確かめたところ、&__elem のルールは丸ごと読み飛ばされました。.card__title { } のようにクラス名をそのまま書くか、Sass でコンパイルした CSS を配信します。
Q. CSSネストは何段まで重ねてよいですか? A. 段数に決まった上限の目安はありませんが、重ねるほどセレクタが長くなり詳細度も上がります。2〜3段までにして、それより深くなるときは別のクラスを付けて平らに書くと、後から上書きしやすくなります。
Q. Sass から CSSネストに移すとき、何を確認すればよいですか?
A. &__ や &-- の連結、親に ID とクラスを混ぜたセレクタリスト、入れ子の後ろに書いた宣言の3点です。この記事の書き換え対応表に沿って直し、対象ブラウザに & 必須期の版が含まれるかをバージョン表で確かめます。
まとめ
- CSSネストはブラウザが直接読む標準機能。Chrome 120・Firefox 117・Safari 17.2 以降は
&なしで要素名からも始められる - 子孫・コンビネータは
&を省略でき、疑似クラス・疑似要素・クラスの重ねがけ・親を後ろに置く形は&が必要 &__elemの連結は書けない。ルールごと読み飛ばされるので、クラス名をそのまま書く- 詳細度は
:is()相当。親のリストに ID があると、Sass と同じコードでも結果が変わる。抑えるなら:where() - ネストの後ろの宣言は書いた順のまま扱われる(Chrome 130・Firefox 132・Safari 18.2 以降)。宣言は先頭にまとめると版による差が出ない
- @media・@container・@layer・@scope・@supports もネストできる。フォールバックは
@supports selector(&)か、平らな CSS への変換で行う
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「CSSネストの書き方|&の使い方とSassとの違い・対応ブラウザ」
株式会社RJCでは、CSSネストをはじめとするCSSの新しい書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。