三段階で決まる
一言でいうと
同じプロパティに複数のルールが適用されるとき、ブラウザーは重要度 → 詳細度 → 順序という3つの段階で1つを選びます。前の段階で決まれば、後ろは見ません。
なぜこれを覚えておくのか
CSSで最も時間を取られる瞬間は、文法を知らないときではなく、確かに書いたのに効かないときです。開発者ツールにはルールがはっきり見えているのに、取り消し線が引かれています。
このとき、理由を知らないままセレクターをもう1つ長く書いたり、ルールをもう1つ重ねたりして済ませると、次の人はもっと長いセレクターを書くことになり、その次の人は!importantを使います。数か月後には、消してもよい行がどこなのか、誰もわからないファイルが残ります。CSSが崩れていく道筋は、ほとんど必ずこれです。
3つの段階を知っていれば、その場で負けたならなぜ負けたのかに答えられます。詳細度のせいか、順序のせいかを切り分けるだけで、直し方がまったく変わります。
1段階目: 重要度
사용자 !important > 작성자 !important > 작성자 일반 > 사용자 일반 > 브라우저 기본
ここでは!importantが勝ちます。そのため、急いでいるときに手が伸びます。しかし、!importantに勝つ方法は、別の!importantだけなので、一度使うと、そのプロパティは永遠にその方式でしか扱えなくなります。それが、CSSが崩れていく典型的な道筋です。
!importantを使いたくなったら、たいてい本当の問題はセレクターの設計です。
2段階目: 詳細度
3桁の数字で数えます。
| 位 | 数えるもの | 例 |
|---|---|---|
| a | ID | #nav |
| b | クラス・属性・擬似クラス | .btn、[open]、:hover |
| c | タグ・擬似要素 | div、::before |
a → 0,0,1
.btn.primary → 0,2,0
#nav a:hover → 1,1,1
ul > li::marker → 0,0,3
位の間では繰り上がりません。クラスを11個付けても、ID1つには勝てません。0,11,0 < 1,0,0です。
特別なものが2つあります。
:where(...)は常に0です。:where(.card) .titleは0,1,0です。ライブラリが「上書きしやすい基本値」を与えるときに使います:is(...)と:not(...)は、かっこの中で最も高いものに従います
3段階目: 順序
ここで、人が最もよくつまずきます。
重要度も詳細度も同じなら、あとに書いたほうが勝ちます。ファイルの中での位置であって、画面上の意味ではありません。
@media (max-width: 900px) {
.side { position: fixed; } /* 모바일 덮개 */
}
.side { position: sticky; } /* ← 나중에 썼다 */
このコードは、モバイルでもstickyになります。メディアクエリの中にあるからといって、より強いわけではありません。詳細度が同じなので(どちらも0,1,0)、あとに書いたほうが勝ちます。
基本ルールは、常にメディアクエリよりも上に置きます
これは、このサービスで実際に起きたバグです。モバイルの目次オーバーレイが開かず、原因は、基本の.lessonSide { position: sticky }が@mediaブロックの下にあったことでした。ブロックを1つ上へ移して直しました。
継承はカスケードではありません
color、font-family、line-heightのようなものは、子に継承されます。margin、padding、display、borderは継承されません。
継承で受け取った値は、その要素に直接指定されたどのルールよりも弱いです。詳細度がどれだけ低くても、直接指定されたほうが勝ちます。継承された値は、「ルールが何もないとき」にだけ使われます。
カスタムプロパティは継承されます
:root { --accent: #3b82f6; }
.btn { background: var(--accent); }
--accentは継承されるため、:rootに置けばどこからでも使え、特定の下位のツリーでだけ上書きできます。
.dark-panel { --accent: #93c5fd; } /* 이 안의 .btn 들만 색이 바뀐다 */
プリプロセッサーの変数(SCSSの$)と決定的に違う点が、これです。SCSSの変数はコンパイル時に消えますが、カスタムプロパティはランタイムに生きていて、カスケードに乗ります。テーマの切り替えがJavaScriptの1行でできる理由です。
グリッドで本当にはまること
グリッドは学びやすいですが、1つ知らないと大きな痛手を負います。
display: noneの子は、グリッドから完全に外れます。場所を空けておきません。
.layout { grid-template-columns: 0 1fr; } /* 접었을 때 목차를 0px 로 */
.layout.hidden .side { display: none; }
意図は「目次を0pxに折りたたむ」ことでした。ところが、display: noneなので目次がフローから外れ、本文が最初の列(0px)に押し込まれます。文字ごとに改行されて、ページが33,000pxになりました。
これも、このサービスで実際に起きたバグです。直し方は1行です。
.layout.hidden { grid-template-columns: 1fr; } /* 칸을 아예 하나로 */
列を0にするのではなく、列の数を減らす必要があります。子が消えたなら、列も消えなければなりません。
いつグリッドで、いつフレックスなのか
- グリッド: 2つの方向を同時に決めます。ページの骨格やカードの格子に使います
- フレックス: 1行(または1列)の中で分けます。ツールバー、ボタンのまとまり、リストの項目に使います
迷ったら、こう問います。行と列の両方を決めたいか。そうであれば、グリッドです。
フレックスで最もよく使うのは、justify-content: space-betweenとalign-items: centerとgapの3つです。margin-left: autoで1つだけを右に寄せるのもよく使います。
現場で診断する順番
CSSが「効かない」ときは、ルールを足す前に、順番に確認します。
- セレクターがその要素に合っているか: 開発者ツールでルールが見えるか
- ほかのルールに負けているか: 取り消し線が引かれていれば、負けています
- 負けているなら、なぜか: 詳細度か、順序か
- 継承された値を期待していないか: 直接指定されたルールがあれば、継承は使われません
!importantは、この4つをすべて確認しても答えが見つからないときにだけ使います。たいてい、3つ目で決着します。
実務でこの順番が重要なのは、直し方が段階ごとに違うからです。詳細度に負けたならセレクターを手直しする必要があり、順序に負けたならファイルの読み込み順やルールの位置を移す必要があります。原因を切り分けずに両方を同時にいじると、直ったのか、たまたま通ったのかもわかりません。