最近、CSSの新機能 @scope が主要ブラウザでほぼ揃ってサポートされるようになってきました。
特に Firefoxではバージョン146以降でサポートが入る予定で、Chrome/Safari/Edgeなどとあわせて、ようやく「普通にCSSとして使える」段階に近づいてきました。
(対応状況の詳細は Can I use でも確認できます)
個人的にも以前から気になっていた機能だったので、実案件で使う前提で一度整理しておこうと思い、この記事を書いています。
いままで「特定のコンポーネントだけにスタイルを効かせたい」という場面では、
- BEMでクラス名を丁寧に設計する
- .component .inner .title のような長いセレクタを書く
- Utilityクラスで逃がす
などといった方法が一般的でした。
ただ、既存プロジェクトやLPの実装などでは、そこまで大規模な仕組みを入れるほどでもない場合も多く、**もっと手軽にスコープを区切れる方法があれば…**と思うこともあります。
その“中間の解決策”を狙っているのが@scopeです。
この記事では、@scopeがどういうものか、そして実務でどう役立つかを中心にまとめていきます。
INDEX
スタイルのスコープ管理が難しい理由
冒頭でも触れましたが、CSS でスタイルの影響範囲をうまく区切るのは意外と難しいです。
よくある課題をあらためて整理してみます。
クラス名の衝突
複数のコンポーネントで .titleを使いたい場合、そのまま書くと全部の.titleに同じスタイルがかかってしまいます。
/* カードコンポーネント用のタイトル */
.title {
font-size: 1.2rem;
color: #333;
}
/* サイドバー用のタイトル */
.title {
font-size: 1rem;
color: #666;
}
後から書いたほうに上書きされてしまうため、どちらかが意図しない見た目になります。
長いセレクタ地獄
衝突を避けようとして、親要素をずっとたどる深いセレクタを書くケースもあります。
.card-component .card-inner .card-title {
font-size: 1.2rem;
}
.sidebar-component .sidebar-inner .sidebar-title {
font-size: 1rem;
}
これでも動作しますが、
- HTMLの構造が変わるとCSSの修正が必要
- 詳細度が高くなり、上書きが難しくなる
など、後で困るポイントが増えがちです。
BEMのハードル
BEMなどの命名規則を使えばクラス名の衝突は避けられますが、既存プロジェクトに新たに導入したり、既存のCSSを規則に合わせて修正するのは意外と大変です。
特に、すでに複雑になったHTML構造やCSSが多い場合は、クラス名の書き換えやセレクタの調整が必要になり、手間と時間がかかる場合もあります。
@scopeでどう解決できるか
こうした問題は、@scope を使うとかなりシンプルに扱えます。
@scope (.card-component) {
.title {
font-size: 1.2rem;
color: #333;
}
}
@scope (.sidebar-component) {
.title {
font-size: 1rem;
color: #666;
}
}
このように、
- .titleのようなシンプルなクラス名を維持しつつ
- コンポーネントごとにスタイルの影響範囲を限定できる
というのが@scopeの大きな特徴です。
長いセレクタを書く必要もなく、クラス名が衝突することもありません。
基本的な使い方
@scope でスコープを設定する
@scopeは、CSSの適用範囲を特定のDOMサブツリーに限定するための at-rule です。
CSSが本来もつ「グローバルに影響が広がる」性質を抑え、
「このコンポーネントの中だけ」「この要素の配下だけ」という形でローカルに閉じたスタイルを書けます。
基本的構文
@scope (スコープルート) {
/* ここに書いたセレクタは、スコープルートの配下だけに適用される */
.title {
color: blue;
}
}
これでスコープルート配下の.titleだけにスタイルが効きます。
他の.titleは影響を受けません。
:scopeセレクタと&(ネスト)でルートや子も指定可能
@scopeブロックの内部では、スコープのルートを指すための特別な疑似クラス:scopeが使えます。
これはルートにマッチした要素そのものを指します。
@scope (.card) {
:scope {
background: #f5f5f5;
}
img {
border: 1px solid gray;
}
}
CSSNesting(ネスト構文)では、&がスコープルートの置き換えとして評価されます。
内部では:is(.card)のように扱われ、ルートを保持したまま展開されます。
@scope (.card) {
& .title { /* 実質 .card .title と同義 */
font-weight: bold;
}
}
自然な書き方のまま「.card の中だけ」という制限が効くので、BEM的な冗長さが減ります。
“ドーナツスコープ (donut scope)” で上下に境界を設定する
@scopeは、ルート(上限)だけでなく下限(リミット)も決められます。
この2つにはさまれた範囲だけにスタイルが効くのが donut scope です。
@scope (.article-body) to (footer) {
img {
border: 2px solid black;
}
}
- .article-bodyの下
- footerの上
- この間にあるimgだけが対象
HTML が複雑なページでも「ここだけに効かせたい/ここより深い階層は除外したい」といった調整がしやすくなります。
注意点
@scopeは便利ですが、いくつか知っておきたいポイントがあります。
- 詳細度(specificity)は変わらない
→ あくまで「適用範囲が狭まるだけ」 - 継承や CSS 変数の伝播は止まらない
→ colorやfont-familyはスコープの外にも影響することがある
スコープ化されるのは “セレクタ” の範囲だけ、という点は押さえておきたいところです。
まとめ
@scope は、既存のCSSのルールを大きく変えずに、コンポーネント単位でスコープを区切れるちょうどいい機能です。
Firefoxバージョン146の対応で主要ブラウザが揃う見込みなので、来年あたりからは実務でも普通に選択肢になっていきそうです。