レイヤー外の CSS 宣言は詳細度と無関係にレイヤー内すべてに勝つ — 要素セレクタを base レイヤーへ入れないとユーティリティが黙って効かなくなる
フロントエンド
CSS
ハマりどころ
知識
判断
ユーティリティファーストの CSS フレームワークがカスケードレイヤーを使う構成で、グローバル CSS に書いた要素セレクタをレイヤーの外に置くと、その要素に対するユーティリティが一切効かなくなる。
機序
カスケードの優先順位では、レイヤーに属さない宣言が、あらゆるレイヤー内の宣言より強い。詳細度はレイヤー間の比較に使われないので、要素セレクタ1つ(詳細度 0,0,1)がクラスセレクタ(同 0,1,0)に勝つ。「詳細度ではクラスが勝つはず」という直感がそのまま裏切られる。
フレームワークを読み込む行の後ろに素直に書いたルールは、見た目上「フレームワークの後なので後勝ち」と思えるが、実際にはレイヤー外として全てに優先する。
見つけにくさ
不具合が「リンクの文字色が背景と同じになる」のような形で出ると、要素もテキストも DOM に存在するため、HTML の目視・テキスト存在ベースの E2E・コードレビューのいずれでも拾えない。実際に画面を見た人間が唯一の検知経路になり得る。
判断基準
- グローバル CSS の要素セレクタは必ず base 相当のレイヤーに入れる。フレームワークのリセットも同じレイヤーにいるので、同レイヤー内のソース順後勝ちで意図どおりになる
- レイヤー外の
@mediaや@supportsの中に書いても同じことが起きる。「トップレベルに直接書かなければ安全」ではない - 逆に、意図してユーティリティに勝たせたいグローバルルールがあるなら、レイヤー外に置くのは有効な手段だが、意図をコメントで明示する。無意識に置いた場合と区別がつかない
検知方法
グローバル CSS を波括弧の深さで走査し、レイヤーに囲まれていない要素セレクタを列挙してゼロを要求する単体テストを置ける(ビルド不要・数十行)。書くときの落とし穴が2つある。
- トップレベルの
;終端文(レイヤー順宣言など)でセレクタ蓄積をリセットしないと、直後の要素セレクタが前の@文と連結し、at-rule と誤認して見逃す - レイヤー外の at-rule の中を走査しないと、
@mediaに包んだ同型のバグを見逃す
このテストが見るのは「配置」だけで、色が背景と同一になること自体は検知しない。限界をテスト名に書いて、コントラストの検査と混同させない。