CSSの@scopeで、スタイルが効く範囲を限る方法
CSS の @scope を使うと、「.card の中の p だけ」のように、スタイルが効く範囲を要素で区切れます。to を付ければ、「ただし .content から先は除く」という下の境界も決められます。セレクターを長くして範囲を絞る必要がないので、詳細度が上がらず、HTML の構造を変えたときに CSS を書き直す量も減ります。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。確かめたい書き方ごとに検証用のページを作り、Chromium で開いて、要素の文字色を getComputedStyle() で調べました。Firefox と Safari は試していません。
範囲の上の境界になる要素を、かっこの中に書く
@scope のかっこには、範囲の起点になる要素のセレクターを書きます。この要素をスコープルートと呼びます。ブロックの中に書いたルールは、スコープルートの中の要素にだけ効きます。
@scope (.card) {
p { color: red; }
}
<div class="card"><p>カードの中</p></div>
<p>カードの外</p>
検証環境で試したところ、赤になったのは「カードの中」の段落だけでした。「カードの外」の段落は黒のままです。
かっこの中は、カンマで区切って複数書けます。@scope (.hero, .body) { … } と書くと、.hero と .body のどちらの中にも同じルールが効きました。
to で、範囲の下の境界を決める
to の後ろのかっこに書いた要素(スコープリミット)から先は、範囲の外になります。
@scope (.card) to (.content) {
p { color: #d85878; font-weight: bold; }
}
次の画像は、カードの中に .content を入れて試したものです。.content の前と後ろにある段落はピンクの太字になり、.content の中の段落には効いていません。

このように上と下に境界がある範囲は、一般に「ドーナツスコープ」と呼ばれています(MDN の @scope のページ)。同じページによると、上の境界(スコープルート)は範囲に含まれ、下の境界(スコープリミット)は含まれません。検証環境でも、@scope (.card) to (.content) { div { color: red; } } と書くと、.card の中のほかの div は赤になり、.content の div そのものは黒のままでした。
部品の中に別の部品を入れるときに役立ちます。カードの見た目を決める CSS を @scope (.card) to (.slot) のように書いておけば、カードの中の .slot に差し込んだ別の部品の段落には、カードの p のスタイルが効きません(上の .content と同じ仕組みです)。
ブロックの中のセレクターは、スコープルートの子孫に一致する
@scope のブロックの中に書いたセレクターは、先頭に「スコープルートと子孫結合子(空白)」が省略されているものとして扱われます(CSS Cascading and Inheritance Level 6 の仕様)。p と書いても :scope p と書いても、一致する要素は同じです(詳細度は違います。後の節で説明します)。
そのため、スコープルートそのものと同じセレクターを書いても、ルートには一致しません。
@scope (.card) {
.card { color: red; }
:scope { background-color: blue; }
}
<div class="card">外側のカード
<div class="card">内側のカード</div>
</div>
検証環境では、.card { color: red; } で赤になったのは内側のカードだけで、外側のカード(スコープルート)は黒のままでした。スコープルートそのものにスタイルを当てるには、:scope を使います。上の例では、:scope の青い背景が外側のカードに付きました(内側のカードも .card なので、そこを起点にした範囲のルートになり、同じく青い背景が付きました)。
先頭に > を書くと、スコープルートの直接の子だけに絞れます。@scope (.card) { > p { color: red; } } では、.card の直接の子の段落は赤になり、間に div をはさんだ孫の段落は黒のままでした。
入れ子になったとき、近いスコープルートのスタイルが勝つ
明るいテーマと暗いテーマの部品を入れ子にすると、@scope の効き目がよく分かります。
<div class="light">
<div class="dark">
<div class="light"><p>いちばん内側</p></div>
</div>
</div>
.light p と .dark p のような子孫セレクターで書くと、いちばん内側の段落には両方が一致します。詳細度が同じなので、後に書いた .dark p が勝ち、明るいテーマの中なのに白い文字になります。
@scope で書くと、段落から近いほうのスコープルートのスタイルが勝ちます。
@scope (.light) { p { color: #1f2a30; } }
@scope (.dark) { p { color: #ffffff; } }

検証環境で試したところ、@scope を使わない書き方では、いちばん内側の段落は白になりました。@scope を使う書き方では、黒(#1f2a30)になりました。いちばん内側の段落から見ると、.light のスコープルートは1つ上、.dark のスコープルートは2つ上にあります。この近さを、仕様ではスコープの近さ(scope proximity)と呼びます。
ただし、近さで決まるのは、重要度やレイヤー、詳細度が同じときだけです。仕様に書かれたカスケードの順序では、近さは詳細度の次、書いた順番の前に比べます。検証環境で、.dark のほうだけ p.text { color: blue; } と詳細度を上げると、いちばん内側の段落は、遠い .dark の青になりました。
同じ仕様では、@scope に入っていないルールは、スコープルートから無限に遠いものとして扱います。そのため、詳細度が同じなら、@scope の中のルールは、後に書いた @scope の外のルールにも勝ちます。検証環境で、@scope (.card) { p { color: red; } } の後に p { color: blue; } を書いたところ、.card の中の段落は赤のままでした。
スコープルートのセレクターは、詳細度に入らない
@scope (#box) のように、かっこに ID セレクターを書いても、中のルールの詳細度は上がりません。MDN の @scope のページによると、ブロックの中のセレクターは、先頭に :where(:scope) が付いているかのように扱われます。:where() の詳細度は 0 なので、p の詳細度は 0-0-1 のままです。
@scope (#box) {
p { color: red; }
}
.note { color: blue; }
<div id="box"><p class="note">文字</p></div>
検証環境では、文字は .note の青になりました。詳細度 0-1-0 の .note が、0-0-1 の p に勝っています。
:scope を自分で書くと、そのぶん詳細度が上がります。上の p を :scope p に変えると、検証環境では文字は赤になりました。MDN の @scope のページの詳細度の節によると、:scope は擬似クラスとして 0-1-0 と数えるので、:scope p の詳細度は 0-1-1 です。スコープルートを #box にしても、ID セレクターの重みは加わりません。
& を使った & p は、検証環境では p と同じく .note に負け、文字は青でした。MDN のページには、@scope の中の & の詳細度の扱いがブラウザやそのバージョンによって違うと書かれています。& で詳細度を調整するつもりなら、使うブラウザで確かめてからにしてください。
@scope の中のルールの詳細度は、かっこの中のセレクターではなく、ブロックの中に書いたセレクターだけで決まります。ルートを ID で指定しても、上書きしにくいスタイルにはなりません。
継承するプロパティは、スコープリミットの中にも伝わる
@scope が区切るのは、セレクターが一致する要素の範囲です。親から子へ受け継がれるプロパティの値までは止めません。
@scope (.card) to (.content) {
:scope { color: red; }
}
<div class="card">
<div class="content"><p>本文</p></div>
</div>
検証環境では、.content の中の「本文」も赤になりました。color はスコープルートの .card に指定され、それが子の要素に受け継がれています。MDN の @scope のページにも、color や font-family のような継承されるプロパティは、範囲を越えて受け継がれると書かれています。スコープリミットの中で文字色を変えたくないなら、リミットの側でも色を指定します。
HTML の中の style 要素に書くと、親要素がスコープルートになる
かっこを省いて @scope { … } とだけ書くこともできます。この形を <style> 要素の中に書くと、その <style> 要素の親要素がスコープルートになります(MDN の @scope のページ)。
<section>
<style>
@scope {
p { color: red; }
}
</style>
<p>section の中</p>
</section>
<p>section の外</p>
検証環境では、赤になったのは「section の中」の段落だけでした。部品の HTML と、その部品のためだけの CSS を、1か所にまとめて書けます。
対応しているブラウザ
MDN のブラウザ互換性データによると、@scope に対応したのは Chrome 118、Firefox 146、Safari 17.4 です。Safari は 26 で、@scope の中のルールが <input> と <textarea> に効かなくなり(一部対応の扱い)、26.4 で解消されています。





