@scopeでCSSスタイルの適用範囲を制御する
@scopeはCSSスタイルの適用範囲を特定のDOM部分木に限定できるアットルールです。Baseline 2025で利用可能になりました。スコープルートとスコープリミットを使ったドーナツスコープや、スコープの近接性によるスタイル優先順位の決定など、強力な機能を持っています。
はじめに
CSSでコンポーネントのスタイルを書くとき、スタイルが意図しない要素に適用されてしまったり、詳細度が高すぎるセレクタを書いてしまったりすることがあります。
@scopeアットルールは、このような問題を解決するためにCSSスタイルの適用範囲を特定のDOM部分木に限定できる機能です。Firefox 146での実装により、Baseline 2025として主要ブラウザすべてで利用可能になりました。
基本的な構文
@scopeには2つの使い方があります。
スタイルシート形式
CSSファイル内でスコープルート(適用範囲)を指定します。
@scope (.card) {
/* .card内のh2要素にのみ適用 */
h2 {
color: var(--color-primary);
}
/* .card内のp要素にのみ適用 */
p {
color: var(--color-text);
}
}
インライン形式
HTMLの<style>要素内で使用する場合、親要素が自動的にスコープルートになります。
<div class="card">
<style>
@scope {
/* .card内のh2要素にのみ適用 */
h2 {
color: var(--color-primary);
}
}
</style>
<h2>カードのタイトル</h2>
</div>
ドーナツスコープ
@scopeをスタイルシート形式で記述するときは、スコープルートの他にスコープリミットも指定できます。
スコープリミットを指定することで、スタイルの適用範囲に下限を設けることができます。 このように上限(スコープルート)と下限(スコープリミット)があるスコープは「ドーナツスコープ」と呼ばれます。
@scope (.article) to (.nested-content) {
img {
border: 2px solid var(--color-border);
border-radius: var(--radius-md);
}
}
この例では、.article内のすべての<img>要素にスタイルが適用されますが、.nested-content内の<img>は除外されます。
<div class="container">
<article class="article">
<p>この記事の内容</p>
<!-- スタイルが反映される -->
<img src="image1.jpg" alt="記事の画像1" />
<div class="nested-content">
<p>ネストされたコンテンツ</p>
<!-- スタイルが反映されない -->
<img src="image2.jpg" alt="記事の画像2" />
</div>
</article>
<!-- スタイルが反映されない -->
<img src="image3.jpg" alt="記事の画像3" />
</div>
Playground
ドーナツスコープ
.article内の画像(スタイル適用対象)
.nested-content内の画像(除外される)
@scope (.article) to (.nested-content) {
img {
border: 3px solid var(--cyan-500);
border-radius: var(--radius-md);
}
}:scope疑似クラス
@scope内からスコープルートにスタイルを記述するには:scope疑似クラスを使用します。
@scope (.card) {
:scope {
background: var(--color-bg-subtle);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
}
h3 {
margin-bottom: var(--spacing-sm);
}
}
:scope疑似クラスは、スコープリミットでも利用できます。
@scope (.article) to (:scope > .nested-content) {
img {
border: 2px solid var(--color-border);
border-radius: var(--radius-md);
}
}
この場合、.article内のすべての<img>要素にスタイルが適用されますが、.nested-contentがスコープルートの直接の子である場合に限り、その中の<img>は除外されます。
<div class="container">
<article class="article">
<p>この記事の内容</p>
<!-- スタイルが反映される -->
<img src="image1.jpg" alt="記事の画像1" />
<div class="nested-content">
<p>ネストされたコンテンツ</p>
<!-- スタイルが反映されない -->
<img src="image2.jpg" alt="記事の画像2" />
</div>
<div>
<div class="nested-content">
<p>ネストされたコンテンツ</p>
<!-- スタイルが反映される -->
<img src="image2.jpg" alt="記事の画像2" />
</div>
</div>
</article>
<!-- スタイルが反映されない -->
<img src="image3.jpg" alt="記事の画像3" />
</div>
上記のHTMLは、.article内の最初の.nested-content内の<img>にはスタイルが適用されませんが、2番目の.nested-content内の<img>にはスタイルが適用されます。
Playground
:scopeを使ったスコープリミット
.article(スコープルート)
.nested-content(直接の子 → 常に除外)
ラッパー要素
.nested-content(孫要素 → :scope >の時は適用される)
@scope (.article) to (:scope > .nested-content) {
/* .nested-contentがスコープルートの直接の子の場合のみ除外 */
img {
border: 3px solid var(--cyan-500);
}
}スコープの近接性
@scopeによって、「スコープの近接性」という新しい基準が導入されました。
「スコープの近接性」では、同じ詳細度のスタイルが競合した場合、スコープルートまでのDOM距離が短いほうを優先します。
@scope (.info-box) {
.message {
color: var(--color-fg-info);
background-color: var(--color-bg-info);
}
}
@scope (.warning-box) {
.message {
color: var(--color-fg-warning);
background-color: var(--color-bg-warning);
}
}
<div class="info-box">
<p class="message">情報メッセージ</p>
<div class="warning-box">
<p class="message">警告メッセージ</p>
<div class="info-box">
<p class="message">ネストされた情報メッセージ</p>
</div>
</div>
</div>
最も内側の.messageは、.info-boxスコープまでの距離が.warning-boxスコープまでの距離より短いため、.info-boxのスタイルが適用されます。
@scopeがない場合、同じ詳細度のスタイルが競合したときは、後に定義された.warning-boxのスタイルが優先されます。
Playground
スコープの近接性
@scopeがONの場合、最も内側の<p>は近接性により.info-boxのスタイルが適用されます@scopeがOFFの場合、通常のカスケードで.warning-boxのスタイルが優先されます(後に定義されているため)
@scope (.info-box) {
.message { color: var(--color-fg-info); background: var(--color-bg-info); }
}
@scope (.warning-box) {
.message { color: var(--color-fg-warning); background: var(--color-bg-warning); }
}@scopeの注意点
スタイルの継承は制限されない
@scopeはセレクタの適用範囲を制限しますが、CSSの継承は制限しません。
つまり、@scopeはShadow DOMのような完全なカプセル化ではなく、スタイルが適用される範囲を狭めるだけの役割を持ちます。
@scope (.container) to (.nested) {
p {
color: red;
}
}
この場合、.nested内の<p>にはスタイルが直接適用されませんが、.container直下の<p>からcolorプロパティが継承される可能性があります。
スコープ外の要素は選択できない
.cardの外側にある要素を選択しようとすると、無効なセレクタとして扱われます。
@scope (.card) {
/* 無効:スコープ外の要素を選択しようとしている */
:scope + .sibling { }
}
@scopeはスコープルート内のスタイルを管理するための機能であり、スコープ外への影響を意図的に制限しています。
おわりに
@scopeを紹介しました。
これまで、特定の範囲にのみスタイルを適用することは簡単ではなく、CSS Modulesやスタイリングライブラリでスコープを制御したり、BEM記法で命名規則による衝突回避をしてきました。
@scopeにより、純粋なCSSだけでスタイルの適用範囲を柔軟に制御できるようになります。
特に、ドーナツスコープによる部分的な除外や、スコープの近接性による直感的なスタイル優先順位は、コンポーネントベースの開発において強力な武器の一つになると思います。
Baseline 2025として主要ブラウザすべてで利用可能になった今、ぜひ活用してみてください。