CSS Cascade Layers(@layer)入門:優先順位を“設計として固定”して、上書き地獄を終わらせる

はじめに

CSSのつらさは、だいたい「どれが勝っているのか分からない」に集約されます。開発が進むほど、クラスが増え、コンポーネントが増え、ページ別の例外が増え、最終的に「上書きの上書き」になっていく。すると、修正のたびに副作用が出て、!important を投入し、さらに壊れやすくなる……という負のループに入ります。

この問題は「セレクタを綺麗に書く」だけでは完全には解決しません。理由は単純で、CSSの勝敗は ソース順詳細度重要度 の組み合わせで決まるからです。規模が大きくなるほど、ルールの衝突は避けられません。

そこで効くのが CSS Cascade Layers(@layer) です。@layer を使うと、CSSの優先順位を「偶然の順番」ではなく、設計で固定できます。
この記事では、@layer を「導入して終わり」ではなく、チームや長期運用で効く形に落とすための考え方と、すぐ試せるハンズオンをまとめます。


座学

1) そもそもCSSの勝敗は何で決まる?

CSSは最終的に「ある要素のあるプロパティ」に対して、候補となる宣言が複数出てきたとき、どれを採用するかを決めます。大ざっぱに言うと、以下の順で勝敗が決まります。

  1. 重要度(importance)!important がある方が強い
  2. レイヤー(layer order)@layer で定義した“層の順番”
  3. 詳細度(specificity):id / class / 要素…の強さ
  4. ソース順(source order):後に書かれた方が強い

ここで重要なのは、@layer は「詳細度やソース順よりも上に来るルール」を提供する、ということです。つまり、どれだけ強いセレクタを書いても、レイヤーの順番が負けていれば負けます(ただし !important は別枠で強い)。

これが「設計として固定できる」の意味です。
“コンポーネントの見た目は、ユーティリティより強い/弱い” のようなルールを、コードの偶然ではなく仕様として決められます。


2) @layer で何が変わる?(運用のメリット)

@layer を導入すると、次のような状態に持っていけます。

  • 優先順位の基準が明文化される
    「どこに書いたCSSが勝つべきか」をレイヤーの順で固定できる。
  • “後に読み込まれたCSSが勝つ”依存から脱却できる
    import順・bundle順の微妙な差で壊れにくくなる。
  • 詳細度を上げる必要が減る
    .page .wrapper .component .title みたいな“泣きのセレクタ”を減らせる。
  • 段階的に導入できる
    既存CSSを一気に全部書き換えなくても、まずは新規からレイヤーに乗せられる。

「長期で崩れないCSS」を作るなら、@layer は“上書きルールのガードレール”になります。


3) 代表的なレイヤー設計パターン

プロジェクトで使いやすいレイヤー構成はだいたい次の系統に収束します。

  • reset:リセット/normalize/ブラウザ差分の吸収
  • base:bodyや見出し、リンク、タイポグラフィなど“土台”
  • layout:グリッド、コンテナ、ページ骨組み
  • components:ボタン、カード、フォーム、ナビなど部品
  • utilities.mt-2 的な単発上書き、ヘルパー
  • overrides:緊急避難(最終的に消す前提)

ポイントは2つです。

  1. utilities を強くするか弱くするかを決める
    Tailwind的に「utilities最強」にする設計もあるし、BEM的に「components最強」にする設計もあります。
    どちらでも良いですが、混ぜると破綻しやすい。@layerはここを設計で固定できます。
  2. overrides を“借金置き場”として明示する
    レガシー対応や移行期は例外が出ます。その例外を“どこに置くか”を決めておくと、後で返済できます。

4) @layer の書き方(最低限)

基本形はこれです。

  • レイヤー順を宣言(この順番が最重要)
@layer reset, base, components, utilities, overrides;
  • 各レイヤーにスタイルを入れる
@layer base {
body { line-height: 1.6; }
}
  • レイヤーは分割ファイルにもできる(import時にレイヤー指定も可能)
@import "components.css" layer(components);

そして重要な注意点:

  • レイヤー順は “宣言した順” で固定されます
  • 同じレイヤー内では、いつものCSS(詳細度/ソース順)で勝敗が決まります
  • !important はレイヤーを超えて勝つので、使うならルールが必要

ハンズオン

小さな例で、@layer が「偶然の上書き」を「設計の上書き」に変える感覚を掴みます。

0) 構成

  • index.html
  • style.css

1) HTML(サンプル)

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>@layer ハンズオン</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="container">
<h1>CSS Cascade Layers デモ</h1> <section class="demo">
<h2>ボタン</h2>
<button class="btn">Default</button>
<button class="btn u-danger">Utilityで危険色</button>
<button class="btn is-primary">Component variant</button>
</section>
</main>
</body>
</html>

2) まず“レイヤーなし”で起きがちな事故を見る

style.css を次で開始します(ここでは @layer を使いません)。

/* ベース */
body { font-family: system-ui, sans-serif; }
.container { width: min(100% - 32px, 720px); margin: 0 auto; }/* コンポーネント */
.btn {
padding: 10px 14px;
border-radius: 10px;
border: 1px solid #ccc;
background: #fff;
}/* ユーティリティ(本当は単発上書きのつもり) */
.u-danger {
background: #c00;
color: #fff;
}/* コンポーネントのバリアント */
.btn.is-primary {
background: #111;
color: #fff;
border-color: #111;
}

この状態で「Utilityで危険色」と「Primary」が混ざったり、読み込み順が変わったりすると、意図せず勝敗が変わります。
例えば、CSSが分割されてbundleされると、**“たまたま後ろに来た方が勝つ”**が起きます。


3) @layer を入れて優先順位を固定する

次に style.css@layer で書き換えます。

/* 1) まずレイヤー順を“宣言”して固定する */
@layer base, components, utilities, overrides;/* 2) base */
@layer base {
body { font-family: system-ui, sans-serif; }
.container { width: min(100% - 32px, 720px); margin: 0 auto; }
}/* 3) components */
@layer components {
.btn {
padding: 10px 14px;
border-radius: 10px;
border: 1px solid #ccc;
background: #fff;
} .btn.is-primary {
background: #111;
color: #fff;
border-color: #111;
}
}/* 4) utilities(この例では “componentsより強い” と決める) */
@layer utilities {
.u-danger {
background: #c00;
color: #fff;
}
}

ここで決めたことは明確です。
utilities は components より後(=強い)
だから、.btn.is-primary.u-danger が衝突したとしても、設計として utilities が勝ちます。

逆に「コンポーネントの見た目を守りたい」なら順番を逆にすれば良いです。

@layer base, utilities, components, overrides;

これだけで「CSSの思想」が固定されます。
重要なのは、書いた場所や読み込み順ではなく、レイヤー順が基準になることです。


4) “移行期の借金”を overrides に閉じ込める

既存プロジェクトで @layer を導入すると、どうしても一時的な例外が出ます。そこで overrides を用意します。

@layer overrides {
/* 移行中だけ:特定ページのボタンだけ角丸を変える */
.page-legacy .btn {
border-radius: 4px;
}
}

このルールが「最後に勝つ層」にあることで、例外が散らばりにくくなります。
そして後で、.page-legacy が消えたタイミングで overrides を掃除できます。
例外を“見える化”して、返済できる形にするのが運用で効きます。


5) 分割CSSと @import layer() の考え方(現場向け)

ファイルを分割しているなら、import時にレイヤーを宣言できます。

@layer reset, base, components, utilities, overrides;@import "base.css" layer(base);
@import "components.css" layer(components);
@import "utilities.css" layer(utilities);

「どのファイルがどの層か」が明確になり、読み込み順に依存しなくなります。
(ビルド環境で @import をどう扱うかはプロジェクト次第ですが、考え方としてはこれが綺麗です。)


まとめ

@layer は、CSSの優先順位を“偶然”から“設計”へ引き上げる仕組みです。

  • CSSの勝敗は !important / レイヤー / 詳細度 / ソース順で決まる
  • @layer を使うと、詳細度や読み込み順より先に「どの層が勝つか」を固定できる
  • レイヤー構成を決めることは、CSSの思想(utilities最強か、components最強か)を決めること
  • 移行期の例外は overrides に集約すると、返済可能な借金になる
  • セレクタの過剰な強化や !important 乱用を減らし、保守性が上がる

CSSが大きくなってくるほど @layer の価値は上がります。早めに“層”を決めておくと、後々の変更が軽くなります。


投稿日

カテゴリー:

投稿者:

タグ:

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です