CSS @scope、実戦投入目前。Firefox対応を前に仕組みと使い方を整理する

最近、CSSの新機能 @scope が主要ブラウザでほぼ揃ってサポートされるようになってきました。
特に Firefoxではバージョン146以降でサポートが入る予定で、Chrome/Safari/Edgeなどとあわせて、ようやく「普通にCSSとして使える」段階に近づいてきました。
(対応状況の詳細は Can I use でも確認できます)

個人的にも以前から気になっていた機能だったので、実案件で使う前提で一度整理しておこうと思い、この記事を書いています。

いままで「特定のコンポーネントだけにスタイルを効かせたい」という場面では、

  • BEMでクラス名を丁寧に設計する
  • .component .inner .title のような長いセレクタを書く
  • Utilityクラスで逃がす

などといった方法が一般的でした。

ただ、既存プロジェクトやLPの実装などでは、そこまで大規模な仕組みを入れるほどでもない場合も多く、**もっと手軽にスコープを区切れる方法があれば…**と思うこともあります。

その“中間の解決策”を狙っているのが@scopeです。
この記事では、@scopeがどういうものか、そして実務でどう役立つかを中心にまとめていきます。

スタイルのスコープ管理が難しい理由

冒頭でも触れましたが、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の対応で主要ブラウザが揃う見込みなので、来年あたりからは実務でも普通に選択肢になっていきそうです。

この記事を気に入ったら

こーでィ

こーでィ

2020年入社です。

この人が書いた記事を見る >>