CSS - @scope

目次

概要

書き方

下記の3形式があります。1番目の形式は scope-start で指定したセレクタにマッチする要素をスコープルートとし、その子孫をスコープとします。

@scope (scope-start) {
  ...
}

2番目の形式は、scope-start セレクタにマッチする要素をスコープルートとしますが、scope-limit セレクタにマッチする要素配下はスコープ外とします。これを ドーナツスコープ と呼びます。

@scope (scope-start) to (scope-limit) {
  ...
}

3番目の形式は @scope を含む <style> の親要素をスコープルートとします。

<element>
  <style>
  @scope {
    ...
  }
  <style>
</element>

使用例

基本的な使用例(@scope)

例えば下記のような例を考えます。.card クラスも .comment クラスも .title クラスを持っていますが、これらは別者です。

<div class="card">
  <div class="title">Card Title</div>
  ...
</div>

<div class="comment">
  <div class="title">Comment Title</div>
  ...
</div>

これらを区別するには下記の様に階層をつけて書く方法があります。

.card .title { ... }
.comment .title { ... }

2023年4月の Chrome 112 でサポートされたネスティングを用いる方法もあります。

.card {
  .title { ... }
}
.comment {
  .title { ... }
}

クラス名自体を BEM に従って命名する方法もあります。

<div class="card">
  <div class="card__title">Card Title</div>
  ...
</div>

<div class="comment">
  <div class="comment__title">Comment Title</div>
  ...
</div>

上記の方法に加えて、@scope を用いて区別することが可能となりました。

@scope (.card) {
  .title { ... }
}
@scope (.comment) {
  .title { ... }
}

スコープリミット(to ...)

例えば下記の例で、カード(.card) のスタイルは指定したいけど、引用部(<blockquote>) のスタイルは引用元のスタイルに合わせたい場合を考えます。

<div class="card">
  <div class="title">Card Title</div>
  <div class="description">...</div>
  <blockquote>
    <div class="title">...</div>
  </blockquote>
</div>

この場合、@scope に下記のようなスコープリミットを指定すると blockquote 配下はスコープ範囲外となります。

@scope (.card) to (blockquote) {
  .title { font-weight: bold; }
}

ただし、スコープ外であっても親要素で定義したスタイルが継承されるプロパティの場合はそのまま継承されるので注意してください。

@scope (.card) to (blockquote) {   /* スコープリミットを指定 */
  .title { color: red; }
  .description { color: blue; }    /* 親要素のcolorを指定 */
}
<div class="card">
  <div class="title">Card Title</div>
  <div class="description">
    <blockquote>
      <div class="title">...</div>  /* 親要素の color プロパティはそのまま継承される */
    </blockquote>
  </div>
</div>

スコープ疑似クラス(:scope)

:scope 疑似クラスはスコープのルート要素にマッチします。次の例では .card クラスの要素に対して枠線を描画します。

@scope (.card) {
  :scope {
    border: 1px solid #ccc;
  }
}
<div class="card">
  <div class="title">Card Title</div>
</div>

@scope の詳細度

@scope の中の 詳細度@scope の外の詳細度と同じになると定義されています。下記の例では color:red も color:blue も詳細度は 0-0-1 となります。ただし、同じ優先度のものが @scope 内と @scope 外にある場合、@scope 内の方が優先されるようです。後述のスコープの近接性によるのかもしれません。

@scope (.card) {
  span { color: red; } /* 詳細度は同じだけどこちらが優先される */
}
span { color: blue; }
<div class="card">
  <span>AAA</span>
</div>

スコープの隣接性

下記の例で .light-theme のテキストは黒くなることを期待していますが、内側の .light-theme のテキストは白くなってしまいます。これは、.light-theme p.dark-theme p の詳細度が同じであるため、後に記述したほうが優先されるためです。

CSS
.light-theme { background: #ccc; padding: 8px; }
.dark-theme { background: #333; padding: 8px; }
.light-theme p { color: black; }
.dark-theme p { color: white; }
HTML
<div class="light-theme">
  <p>Light theme text</p>
  <div class="dark-theme">
    <p>Dark theme text</p>
    <div class="light-theme">
      <p>Light theme text</p>
    </div>
  </div>
</div>
表示

Light theme text

Dark theme text

Light theme text

この問題は @scope を用いることで解決できます。@scope(.light-theme) { p }@scope(.dark-theme) { p } も同じ詳細度で .dark-theme の方が後に記述されていますが、@scope では競合する指定がある場合、要素の階層で最も近い階層のルールに従う(スコープの近接性)というルールがあるため、最も近い階層である .light-theme の定義に従います。

CSS
@scope (.light-theme) {
  :scope { background: #ccc; }
  p { color: black; }
}
@scope (.dark-theme) {
  :scope { background: #333; }
  p { color: white; }
}