Claude Media
Claude CodeでCSSの!importantを整理する — cascade layersへの段階移行

Claude CodeでCSSの!importantを整理する — cascade layersへの段階移行

!importantだらけのCSSは、cascade layersへ一気に包むと優先順位が反転します。洗い出し、レイヤー順の宣言、段階移行、見た目差分の確認までをClaude Codeに任せる手順です。

!importantが数百か所に散らばったスタイルシートは、整理の方法を間違えると見た目が静かに壊れます。cascade layers(@layer)へ移せば詳細度の競争から抜けられますが、!importantが残ったままレイヤーに包むと、優先順位がレイヤーの宣言順に従って反転するからです。Claude Codeには洗い出しと機械的な書き換えを任せ、順序の設計と見た目の確認だけを人間が握る分担が現実的です。ここでは診断、レイヤー設計、段階移行、見た目差分の確認を順に扱います。

なぜ一気に@layerで包むと壊れるのか

cascade layersは、レイヤーの宣言順が詳細度より先に効く仕組みです。レイヤー順が決まると、詳細度と記述順は判定に使われなくなります。後ろのレイヤーにあるルールは、前のレイヤーのルールより詳細度が低くても勝ちます。

移行で事故が起きる理由は、通常の宣言と!important付きの宣言で順序が逆になるからです。MDNの@layerと!importantの説明をもとに整理すると、次の表になります。

宣言の種類勝つ側
通常の宣言(レイヤー外vsレイヤー内)勝つ側レイヤー外(unlayered)
通常の宣言(レイヤー同士)勝つ側後に宣言したレイヤー
!important(レイヤー同士)勝つ側先に宣言したレイヤー
!important(レイヤー外vsレイヤー内)勝つ側レイヤー内

最後の行が落とし穴です。レイヤー外の!importantは、レイヤー内の!importantに負けます。既存CSSの一部だけを@layerに入れると、これまで最強だった!importantが、新しくレイヤーに入ったルールの!importantに逆転されることがあります。

つまり、!importantが残っている間は、レイヤー化そのものが挙動を変える操作になります。だから「!importantを減らしてから包む」「包んでから減らす」のどちらでも、順序を決めずにClaude Codeへ「全部レイヤーに移して」と頼む進め方は避けます。

前と後

片方だけ@layerに入れると、詳細度の勝敗が逆転する例

BEFORE

レイヤー化の前

.card aのcolor: blue !importantと#sidebar aのcolor: red !importantは、どちらもレイヤー外の!importantです。詳細度の高い#sidebar aが勝ち、リンクは赤になります。

AFTER

`.card a`だけを@layerに入れた後

レイヤー内の!importantはレイヤー外の!importantより強いため、詳細度が低い.card aが勝ち、リンクは青になります。

手順1:!importantを洗い出し、何と競っているかを書かせる

最初は書き換えさせず、診断だけを頼みます。Claude Codeには、書き換えを走らせる前に提案だけを出させるプランモードがあります。claude --permission-mode planで起動するか、セッション中にShift+Tabで切り替えると、承認するまでファイルは変更されません。

claude --permission-mode plan

まず件数を数えて、規模の見当をつけます。このコマンドは手元で確かめる用のもので、Claude Codeに実行させても同じ結果になります。

grep -rn '!important' src --include='*.css' --include='*.scss' | wc -l
grep -rln '!important' src --include='*.css' --include='*.scss'

次に、診断用のプロンプトを渡します。要点は「各!importantが何を打ち消すために付いたのか」を出させることです。

src 配下の CSS と SCSS から !important を全件洗い出して。ファイルは変更しない。
各箇所について、次の 4 項目を表にして:
1. ファイルと行
2. 対象のセレクタと、そのセレクタの詳細度(ID, クラス, 要素の数)
3. この !important が打ち消していると思われる、競合ルールの場所
4. 分類: (a) サードパーティ CSS への対抗 (b) 詳細度の競争に負けた応急処置
   (c) ユーティリティクラスの意図的な強制 (d) 不明
不明なものは推測せず「不明」と書いて。

分類の(c)は意図的な!importantです。UI要件のために意図して付ける場合は、触らないでほしい理由をCSSのコメントで残します。すべてを消す対象にせず、残すものに理由を付ける作業も、この表から始まります。

分類(d)の扱いが肝です。Claude Codeは競合ルールを静的に読むだけなので、動的に付くクラスやJavaScriptが差し込むインラインスタイルは見落とせます。不明を「不明」のまま出させておけば、後の段階で見た目の確認に回せます。

手順2:レイヤーの順序を先に決める

洗い出しで見えた競合関係から、レイヤーの順序を設計します。順序は@layerの文だけで先に宣言できます。レイヤー名の宣言だけを先頭に置き、あとからルールを同名で追記する書き方ができます。

@layer reset, vendor, base, components, utilities;

左が低く、右が高い順です。ここでは次の割り当てを例にします。

設計

レイヤーに割り当てる中身の例

  • reset と vendor

    リセットCSSとサードパーティCSS。外部のスタイルは最初のレイヤーに入れて、自前のスタイルを詳細度に関係なく上回らせます。

  • base と components

    要素の既定スタイルと、コンポーネント単位のスタイルです。

  • utilities

    .hiddenや.mt-0のような単機能クラス。最後に宣言して、通常の宣言で常に勝たせます。

サードパーティCSSが!importantを含む場合は話が変わります。そのときは上書き用のレイヤーを作って先頭に宣言する方法があります。!importantはレイヤー順が逆になるため、先頭のレイヤーに置いた!importantが最も強くなるからです。

この設計をClaude Codeに任せるときは、順序案を複数出させ、各案でどの!importantの勝敗が変わるかを説明させます。

先ほどの表をもとに、@layer の宣言順の案を 2 つ出して。
それぞれの案で、勝敗が入れ替わる !important の箇所を列挙して。
まだファイルは変更しない。

案を選んだら、プランを承認してから実際の書き換えに入ります。

手順3:低いレイヤーから順に、小さく移す

移行は「ファイル1つ、レイヤー1つ」の単位で進めます。リファクタリングは小さく検証できる単位で進めるのが基本です。

手順

段階移行の順序

  1. 1

    宣言の文を先頭に追加する

    エントリーポイントのCSSの先頭に@layerの宣言文だけを置きます。@importを使う構成では、@importは@charsetと@layerの宣言文を除くすべてのルールより前に置く必要があり、宣言文は@importの前に置けます。@layerブロックを書いたあとの@importは置き場所のルールに反するため、宣言文だけを先頭に置く形にします。

  2. 2

    サードパーティ CSS をレイヤーに入れる

    @import "theme.css" layer(vendor);のように、インポート時にレイヤーを指定します。自前のCSSはまだレイヤー外なので、通常の宣言では自前側が必ず勝ちます。

  3. 3

    リセットと base を入れる

    詳細度の低い要素セレクタ中心のファイルを@layer base { ... }で包みます。ここで見た目が変わらなければ、レイヤー機構自体は問題なく動いています。

  4. 4

    components を 1 ファイルずつ入れる

    !importantを含むファイルは、1つ移すごとに見た目を確認します。

  5. 5

    最後に !important を外す

    レイヤー順で勝てるようになったルールから、!importantを1つずつ外します。

「!importantを外す」のは最後です。外すのが早すぎると、まだレイヤー化していない他のルールに負けて見た目が崩れます。外すときの判断材料は手順1の分類です。(b)の応急処置は、レイヤー順で勝てると確認できた時点で外せます。(a)のサードパーティ対抗は、vendorレイヤーを先頭に置き、外部CSS側に!importantが無ければ、詳細度に関係なく自前のスタイルが勝つため外せる候補になります。(c)は残します。

Claude Codeへの依頼は、1回に「1ファイルを@layer componentsに移す」といった粒度にとどめます。

components/card.css を @layer components で包んで。
中身のセレクタと値は変えない。!important はこの段階では外さない。
包んだ後に、レイヤー化で勝敗が変わりうる箇所があれば列挙して。

「値は変えない」「!importantは外さない」と禁止事項を書くのがコツです。機械的な移動と、意味を変える修正を別のコミットに分けておくと、見た目が崩れた時にどちらが原因かを切り分けられます。

手順4:スクリーンショットで見た目の差分を確かめる

レイヤー化は、CSSの意味を変えずに優先順位の決まり方を変える作業です。テストもtypecheckも通るのに画面だけ崩れる種類の変更なので、画面での確認が検証の中心になります。

Claudeに「合否を返せる確認手段」を渡すと、自走しやすくなります。その手段の例が、モックアップの横に置くブラウザのスクリーンショットです。Chrome連携を有効にすると、Claudeはローカルで動かしているアプリを開き、コンソールやDOMの状態を読めます。Chrome連携の説明には、視覚的な回帰の確認も用途として書かれています。

移行を始める前に、基準になる画像を保存させます。

開発サーバーの /, /pricing, /dashboard の 3 ページを、
デスクトップ幅とスマホ幅の 2 通りで開いて、スクリーンショットを
./css-baseline/ に保存して。ファイル名はページと幅が分かるようにして。

Chrome連携は、スクリーンショットをディスクに保存するよう依頼できます。保存先のパスは結果として報告されます。ただし、Chrome連携は/loginでサインインしている場合に使え、APIキーやclaude setup-tokenのトークンで認証しているセッションでは有効になりません。その場合は、手元で撮った画像を貼り付けるか、パスを渡して比べさせる方法があります。画像はドラッグ&ドロップ、Ctrl+Vでの貼り付け、パス指定のいずれでも渡せます。

1ファイル移すたびに、同じ手順で別フォルダに撮り直させます。

同じ 3 ページを同じ幅で撮り直して ./css-after/ に保存。
css-baseline/ の画像と 1 枚ずつ見比べて、差がある場合は
どの要素のどのプロパティが変わったかを、DOM とスタイルを読んで説明して。
差がなければ「差分なし」と、見比べた画像のパスを添えて。

「差分なし」だけを返させないのが重要です。Claudeに「完了した」と言わせるだけでなく、実行したコマンドの出力やスクリーンショットという証拠を求めます。何と何を見比べたかのパスが残っていれば、後から人間が同じ2枚を開き直せます。

落とし穴

撮影対象に出ない状態は、差分なしと判定される

ホバー、フォーカス、モーダルの展開、ダークモードのように、読み込み直後には現れない状態の!importantは、スクリーンショット比較をすり抜けます。分類(d)の不明な箇所と、疑似クラスに付いた!importantは、撮影の指示にその状態への操作を含めます。

ピクセル単位の機械比較が必要なら、画像を比べるスクリプトを書かせて終了コードを合否にする方法も使えます。確認手段には「出力を固定の期待値と突き合わせるスクリプト」も使えます。判定を終了コードで返す形にすれば、次の節の自動化にもつながります。

手順の型をCLAUDE.mdとStop hookに固定する

セッションが変わると、「!importantを外すのは最後」「1ファイルずつ」の約束も忘れられます。プロジェクトのCLAUDE.mdに書いておくと、毎回同じ前提で作業が始まります。以下は例です。

## CSS の移行ルール(cascade layers)
- レイヤー順は src/styles/index.css 先頭の @layer 宣言が唯一の正。
  順序を変えるときは必ず人間に確認する。
- 1 回の変更は 1 ファイルまで。レイヤー化と !important の削除を同じ変更に入れない。
- 変更後は ./css-baseline/ と同じページ・幅で撮り直して比較し、
  比較した 2 枚のパスを報告する。
- !important を意図して残す場合は、直前に理由をコメントで書く。

公式は、確認用のコマンドをCLAUDE.mdに書くのが最も効く一行だとも述べています。撮影と比較をシェルのスクリプトにまとめてあれば、そのコマンドを書くだけで済みます。

さらに「確認が通るまでターンを終わらせたくない」場合は、Stopフックの出番です。Stopフックはターンを終えようとするたびに指定のスクリプトを走らせ、終了コード2を返すと作業に戻ります。上限回数に達するまで、チェックが通るまでターンを終えられない構成にできます。比較スクリプトの終了コードを合否にしておけば、見た目差分のあるまま「完了」と言われる事態を避けられます。検証コマンドをCLAUDE.mdに固定する考え方は、ruffとmypyの検証ループにも同じ形があります。

完了の判定:何が残れば「整理できた」と言えるか

レイヤー化を終えた段階で、!importantの件数がゼロである必要はありません。判定の基準は次の3点です。

  • 残った!importantのすべてに、残す理由のコメントがある
  • レイヤー順の宣言が1か所にあり、順序の根拠を説明できる
  • 基準画像と移行後の画像の見比べが、全ページ・全幅で記録されている

分類(d)の不明だった項目が残っているなら、整理は途中です。不明を放置したままレイヤー化を済ませると、後でそのルールを触るたびに、同じ診断をやり直すことになります。

最新のCSS機能そのものを使わせたい場合は、モデルの癖を矯正する別の主題です。modern-web-guidanceプラグインで最新CSSを使わせる手順と組み合わせると、整理した土台の上に新規コードも最新の書き方で積めます。スクリーンショットを検証の足場にする運用は、Three.jsの3Dシーンを作る例にも出てきます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/common-workflows
  2. code.claude.com/docs/en/best-practices
  3. code.claude.com/docs/en/chrome
  4. developer.mozilla.org/en-US/docs/Web/CSS/@layer
  5. developer.mozilla.org/en-US/docs/Web/CSS/important
この記事を共有:XはてブLinkedIn