HTML/CSS
投稿日:

スタイルの”巻き添え事故”を防ぐ!CSS @scopeで影響範囲を絞る実装術

スタイルの巻き添え事故を防ぐ!CSS @scopeで影響範囲を絞る実装術

おはようございます!株式会社ファストコーディングの働くおかんです。

最近、子どもたちと一緒にお絵描きをしていたんですが、下の子が絵の具をべたっと塗ったら、隣に置いていた上の子の画用紙にまで色がはみ出してしまって。上の子が「こっちに来んといてー!」と怒っていました。分けて描いていたつもりなのに、境界が曖昧だと隣に影響が出ちゃうんですよね。

CSSでもまったく同じことが起きるんです。複雑なレイアウトを組んでいると、あるセクションに書いたスタイルが別のセクションにまで影響してしまう「巻き添え事故」。.titleとか.textみたいな汎用的なクラス名を使っていると、意図しない場所のデザインが変わってしまいます。まさに、絵の具がはみ出すのと同じです。

今回紹介するCSS @scopeは、まさにこの問題を解決する新しいCSS機能です。「このスタイルはここからここまでだけ」と範囲を限定できるので、複雑なページでもスタイルが漏れません。

CSSの「影響範囲」問題とは

まず、どういう場面で困るのかを具体的に見てみます。

たとえば、トップページに「お知らせセクション」と「サービス紹介セクション」があるとします。どちらにも.titleというクラスを使っています。

/* お知らせセクション用のつもりで書いた */
.title {
  font-size: 18px;
  color: #1e40af;
  border-bottom: 2px solid #1e40af;
}

これ、サービス紹介セクションの.titleにも効いてしまいます。「お知らせの見出しだけ青くしたかったのに、サービス紹介の見出しまで青くなっちゃった」という事故です。

従来の対処法は、クラス名を長くすることでした。

.news-section__title {
  font-size: 18px;
  color: #1e40af;
}

.service-section__title {
  font-size: 20px;
  color: #111827;
}

BEM記法(Block__Element–Modifier)で命名規則を厳格にする方法です。これで解決はしますが、クラス名がどんどん長くなります。HTML側もclass="news-section__title news-section__title--featured"のように膨らんでいく。チームで開発していると、命名規則が統一されないことも多くて、結局ぐちゃぐちゃになりがちなんですよね。

CSS @scopeで「スタイルの壁」を作る

@scopeを使うと、こうなります。

@scope (.news-section) {
  .title {
    font-size: 18px;
    color: #1e40af;
    border-bottom: 2px solid #1e40af;
  }
  
  .text {
    font-size: 14px;
    line-height: 1.8;
  }
}

@scope (.service-section) {
  .title {
    font-size: 20px;
    color: #111827;
  }
  
  .text {
    font-size: 15px;
    line-height: 1.7;
  }
}
<section class="news-section">
  <h2 class="title">お知らせ</h2>
  <p class="text">新サービスを開始しました。</p>
</section>

<section class="service-section">
  <h2 class="title">サービス紹介</h2>
  <p class="text">Webコーディングを中心に提供しています。</p>
</section>

@scope (.news-section)の中に書いた.titleのスタイルは、.news-sectionの中にある.titleにしか適用されません。.service-sectionの中の.titleには一切影響しない。クラス名を長くしなくても、シンプルな名前のまま安全に使えるんです。

夜、子どもが寝た後にこれを初めて試したとき、「もっと早く欲しかった……」と思いました。

@scopeの「下限」指定がすごい

@scopeにはもうひとつ強力な機能があります。スタイルの下限を指定できるんです。toキーワードを使います。

@scope (.card) to (.card__slot) {
  p {
    font-size: 14px;
    color: #374151;
  }
  
  a {
    color: #2563eb;
  }
}
<div class="card">
  <h3>記事タイトル</h3>
  <p>この段落にはスタイルが効きます。</p>
  
  <div class="card__slot">
    <!-- ここから先は @scope の範囲外 -->
    <p>この段落にはスタイルが効きません。</p>
    <a href="#">このリンクも影響を受けません。</a>
  </div>
</div>

@scope (.card) to (.card__slot)は「.cardの中だけど、.card__slotの中は除く」という意味です。カードの外側部分にだけスタイルを当てて、スロット(差し込みエリア)の中身は影響を受けないようにできます。

これが便利なのは、CMSやコンポーネントベースの開発です。カードの中にクライアントが自由にコンテンツを入れるエリアがあるとき、カード自体のスタイルがそのコンテンツに漏れてしまう問題を防げます。

実践1:カードグリッドでのスタイル分離

カードの種類が複数あると、スタイルの衝突が起きやすくなります。@scopeを使えば、カードの種類ごとにスタイルを完全に分離できます。

<div class="card-grid">
  <article class="card-news">
    <h3 class="heading">お知らせタイトル</h3>
    <time class="date">2026.05.09</time>
    <p class="summary">お知らせの概要テキストです。</p>
  </article>
  
  <article class="card-service">
    <h3 class="heading">サービス名</h3>
    <p class="summary">サービスの説明テキストです。</p>
    <a href="#" class="link">詳しく見る</a>
  </article>
</div>
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
}

@scope (.card-news) {
  :scope {
    padding: 20px;
    background: #fff;
    border-left: 4px solid #2563eb;
    border-radius: 8px;
  }
  
  .heading {
    font-size: 16px;
    color: #111827;
    margin-bottom: 4px;
  }
  
  .date {
    font-size: 12px;
    color: #6b7280;
  }
  
  .summary {
    font-size: 14px;
    margin-top: 12px;
    line-height: 1.7;
  }
}

@scope (.card-service) {
  :scope {
    padding: 24px;
    background: #f0f9ff;
    border-radius: 12px;
    text-align: center;
  }
  
  .heading {
    font-size: 18px;
    color: #1e40af;
    margin-bottom: 12px;
  }
  
  .summary {
    font-size: 14px;
    color: #374151;
    line-height: 1.7;
  }
  
  .link {
    display: inline-block;
    margin-top: 16px;
    padding: 10px 24px;
    background: #2563eb;
    color: #fff;
    text-decoration: none;
    border-radius: 6px;
    font-size: 14px;
  }
}

.heading.summaryというシンプルなクラス名をそのまま使えています。BEMで.card-news__headingと書く必要がありません。HTMLがすっきりして、コードレビューもしやすくなります。

:scopeセレクタは、@scopeのルート要素(この場合は.card-news.card-service自体)を指すセレクタです。親要素自体にスタイルを当てたいときに使います。

実践2:ヘッダー・フッター・メインの分離

サイト全体で「ヘッダー内の.link」「フッター内の.link」「メインコンテンツ内の.link」の見た目を変えたい場面は多いです。

@scope (header) {
  .link {
    color: #fff;
    font-weight: 600;
    text-decoration: none;
  }
  
  .link:hover {
    opacity: 0.8;
  }
}

@scope (main) {
  .link {
    color: #2563eb;
    text-decoration: underline;
  }
  
  .link:hover {
    color: #1d4ed8;
  }
}

@scope (footer) {
  .link {
    color: #9ca3af;
    font-size: 13px;
    text-decoration: none;
  }
  
  .link:hover {
    color: #d1d5db;
  }
}

これまではheader .linkのように子孫セレクタで書いていました。動作としては似ていますが、@scopeのほうが詳細度(specificity)が低く保たれるというメリットがあります。

子孫セレクタだと詳細度が積み重なって、あとから上書きしたいときに!importantが必要になることがあるんですよね。@scopeでは、スコープルート部分(headerなど)が詳細度に加算されません。内部のセレクタ単体の詳細度だけで評価されるので、上書きも自然にできます。

実践3:サードパーティ埋め込みへのスタイル漏れ防止

ブログやLPに外部サービスのウィジェット(お問い合わせフォーム、チャットボットなど)を埋め込むことがあります。このとき、ページのグローバルCSSがウィジェット内に漏れて、見た目が崩れることがあるんです。

@scope (.page-content) to (.widget-embed) {
  p {
    font-size: 16px;
    line-height: 1.8;
    color: #374151;
  }
  
  h2 {
    font-size: 24px;
    color: #111827;
    margin-top: 40px;
  }
  
  img {
    max-width: 100%;
    border-radius: 8px;
  }
}

.page-contentの中にはスタイルを当てるけど、.widget-embedの中には一切影響しない。ウィジェットが自前のCSSを持っている場合でも、干渉を避けられます。

ある案件で、お問い合わせフォームのウィジェットを埋め込んだら、ページのCSSimg { border-radius: 8px }がフォーム内のアイコン画像にまで効いてしまったことがありました。@scopeto指定で、この問題がきれいに解決できたんです。

ブラウザ対応状況

@scopeのブラウザ対応状況は以下の通りです(2026年5月時点)。

ブラウザ対応バージョンリリース時期
Chrome118以降2023年10月
Edge118以降2023年10月
Safari17.4以降2024年3月
Firefox146以降2025年12月

2025年12月のFirefox 146で全主要ブラウザが対応完了しました。2026年現在、新規プロジェクトでは安心して使えます。

@scopeに未対応のブラウザでは、@scopeブロック内のスタイルが丸ごと無視されます。スタイルが漏れるのではなく、そのスタイル自体が適用されなくなります。なので、@scopeの外にフォールバック用のスタイルを別途書いておくと、未対応ブラウザでもデザインが崩れにくくなります。レイアウトが壊れるわけではないので、段階的に導入しやすい設計なんですよね。

@scopeとBEM、CSS Modulesとの比較

「BEMやCSS Modulesでも同じことができるのでは?」と思うかもしれません。整理します。

方法スタイル分離ビルド不要HTML簡潔詳細度の制御
BEM(命名規則)×(クラス名が長い)
CSS Modules×(ビルド必要)△(ソースは簡潔、生成HTMLは長い)△(名前の一意化で衝突回避)
@scope

@scopeの強みは、ビルドツールなしで使えて、HTMLもシンプルに保てることです。WordPressのテーマ開発や、ビルド環境がないプロジェクトでも導入できます。

React/Vueを使っているプロジェクトではCSS Modulesやscoped CSSがありますが、静的サイトやCMS案件では@scopeのほうが手軽に導入できるんですよね。

まとめ

今回は、CSS @scopeを使ってスタイルの影響範囲を絞る方法を紹介しました。

押さえておきたいポイントは以下の4つです。

  • @scope (.parent) { } で、スタイルの適用範囲を特定の親要素の中に限定できる
  • @scope (.parent) to (.child) { } で、下限も指定できる。ネストされたコンポーネントへの漏れを防げる
  • BEMのように長いクラス名を書かなくても、シンプルな名前で安全にスタイルを管理できる
  • 2025年12月に全主要ブラウザ対応済み。未対応ブラウザではスタイルが無視されるだけなので、フォールバックも用意しやすい

複雑なレイアウトでスタイルが意図せず漏れる問題は、どのプロジェクトでも起きうることです。特にチーム開発や長期運用のサイトでは、@scopeでスタイルの影響範囲を明示しておくことで、修正時の「別の場所が壊れた」事故を減らせます。

株式会社ファストコーディングでは、こうしたCSS設計の改善やフロントエンドの実装サポートをしています。「CSSが複雑になりすぎて管理できない」「スタイルの衝突を減らしたい」というお悩みがあれば、お問い合わせフォームからお気軽にご相談ください。