TT Lab
はじめる
学ぶ 学習パス コース

CSS — 後に書いた規則が勝つ

三段階で決まる

TT Labで続きを見る

一言でいうと

同じプロパティに複数のルールが適用されるとき、ブラウザーは重要度 → 詳細度 → 順序という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つあります。

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にするのではなく、列の数を減らす必要があります。子が消えたなら、列も消えなければなりません。

いつグリッドで、いつフレックスなのか

迷ったら、こう問います。行と列の両方を決めたいか。そうであれば、グリッドです。

フレックスで最もよく使うのは、justify-content: space-betweenとalign-items: centerとgapの3つです。margin-left: autoで1つだけを右に寄せるのもよく使います。

現場で診断する順番

CSSが「効かない」ときは、ルールを足す前に、順番に確認します。

  1. セレクターがその要素に合っているか: 開発者ツールでルールが見えるか
  2. ほかのルールに負けているか: 取り消し線が引かれていれば、負けています
  3. 負けているなら、なぜか: 詳細度か、順序か
  4. 継承された値を期待していないか: 直接指定されたルールがあれば、継承は使われません

!importantは、この4つをすべて確認しても答えが見つからないときにだけ使います。たいてい、3つ目で決着します。

実務でこの順番が重要なのは、直し方が段階ごとに違うからです。詳細度に負けたならセレクターを手直しする必要があり、順序に負けたならファイルの読み込み順やルールの位置を移す必要があります。原因を切り分けずに両方を同時にいじると、直ったのか、たまたま通ったのかもわかりません。