はじめに
CSSのつらさは、だいたい「どれが勝っているのか分からない」に集約されます。開発が進むほど、クラスが増え、コンポーネントが増え、ページ別の例外が増え、最終的に「上書きの上書き」になっていく。すると、修正のたびに副作用が出て、!important を投入し、さらに壊れやすくなる……という負のループに入ります。
この問題は「セレクタを綺麗に書く」だけでは完全には解決しません。理由は単純で、CSSの勝敗は ソース順 と 詳細度 と 重要度 の組み合わせで決まるからです。規模が大きくなるほど、ルールの衝突は避けられません。
そこで効くのが CSS Cascade Layers(@layer) です。@layer を使うと、CSSの優先順位を「偶然の順番」ではなく、設計で固定できます。
この記事では、@layer を「導入して終わり」ではなく、チームや長期運用で効く形に落とすための考え方と、すぐ試せるハンズオンをまとめます。
座学
1) そもそもCSSの勝敗は何で決まる?
CSSは最終的に「ある要素のあるプロパティ」に対して、候補となる宣言が複数出てきたとき、どれを採用するかを決めます。大ざっぱに言うと、以下の順で勝敗が決まります。
- 重要度(importance):
!importantがある方が強い - レイヤー(layer order):
@layerで定義した“層の順番” - 詳細度(specificity):id / class / 要素…の強さ
- ソース順(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つです。
- utilities を強くするか弱くするかを決める
Tailwind的に「utilities最強」にする設計もあるし、BEM的に「components最強」にする設計もあります。
どちらでも良いですが、混ぜると破綻しやすい。@layerはここを設計で固定できます。 - 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.htmlstyle.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 の価値は上がります。早めに“層”を決めておくと、後々の変更が軽くなります。
コメントを残す