CSS
CSS ・ しーえすえす
Webページの見た目を記述する言語。セレクタで要素を選び、衝突はカスケードで解決する。
✦ 30秒で試す — 航海IDで指定した色が、クラスの色に負けることがある同じファイルにこの順で書くと、リンクは何色になる?予想してみる概要
CSS(Cascading Style Sheets)は、Webページの「見た目」を記述する言語です。HTML が文書の構造を担うのに対し、CSSは色・文字サイズ・余白・配置といった表現を担います。書き方は一貫していて、「セレクタ」で対象の要素を選び、波括弧の中に「プロパティ: 値」の宣言を並べる——h1 { color: navy; } のような規則(ルール)の集合がスタイルシートです。
「構造と見た目を分離する」というのがCSSの根本思想です。同じHTMLでも、当てるCSSを差し替えれば紙面のように印刷向けにも、スマートフォン向けにも姿を変えられます。一つのスタイルシートをサイト全体で共有すれば、デザイン変更が一箇所の修正で済む——この分離が、Webのデザインを保守可能にしています。
なぜ生まれたか
初期の HTML には見た目を指定する手段がほとんどなく、デザインへの要求が高まると <font> タグや bgcolor 属性のような「見た目のためのHTML」が乱立しました。その結果、文書の構造と装飾が混ざり合い、デザインを変えるには全ページのタグを書き換えるしかない、という保守不能な状態に陥っていたのです。
1994年に CERN にいたホーコン・ウィウム・リーが提案した CSS は、「スタイルは文書の外に分離し、規則として宣言する」ことでこれを解決しました(1996年に CSS1 が W3C 勧告)。当時ほかにもスタイル言語の提案はありましたが、CSS が選ばれた決め手は名前にある「カスケード(cascade: 段々に流れ落ちる滝)」です。ページの制作者だけでなく、ブラウザの既定値や、文字を大きくしたい・色を強くしたいといった利用者自身の設定も、同じ要素にスタイルを当てようとします。複数の立場のスタイルが衝突したとき、決まった順序で1つの結果に解決する — この「制作者と利用者の折り合いをつける」仕組みが、CSS を標準に押し上げました。
詳細
カスケード — 「どのスタイルが勝つか」は段を順に比べて決まる
CSSで最初につまずくのが「書いたスタイルが効かない」問題で、その正体はほぼカスケードの優先順位です。同じ要素の同じプロパティに複数の宣言が当たったとき、ブラウザは次の段を上から順に比べ、差がついた段で勝者を決めます。下の段は、上の段がすべて同点だったときにしか使われません。
1段目の「出どころ」は、宣言がどこから来たかです。ブラウザに組み込まれた既定のスタイル(UA スタイルシート。リンクが青いのはこれ)、利用者がブラウザや OS に設定したスタイル、ページの制作者が書いたスタイルの3つがあり、通常の宣言では制作者>ユーザー>ブラウザの順に強くなります。ここに !important が付くと順位が反転し、ブラウザの !important>ユーザーの !important>制作者の !important になります。「制作者が普段は好きに装飾してよいが、弱視の利用者が『文字は必ず黒』と強制した設定は、制作者の !important でも覆せない」という、利用者を守るための設計です。
2段目は、要素の style 属性に直接書いた宣言です。これはセレクタを持たないので、詳細度で比べるのではなく、同じ出どころ・重要度の中でスタイルシートの宣言より先に勝ちます。3段目のカスケードレイヤ(@layer、2022年に主要ブラウザがそろって対応)は、スタイルシートを「リセット」「ライブラリ」「自分のコンポーネント」のような層に分け、層の順番で優劣を決める仕組みです。通常の宣言では後に宣言した層ほど強く、どの層にも入っていないスタイルが最も強くなります。ここで決着がつくので、レイヤの中に書いた ID セレクタは、レイヤの外の要素セレクタ1つにも負けます。詳細度の競争に巻き込まれずに、ライブラリのスタイルを確実に上書きするための道具です。
4段目の詳細度(specificity)は、セレクタの具体性を(ID の数, クラス・属性・擬似クラスの数, 要素名・擬似要素の数)の3桁で数えたものです。#lead は (1,0,0)、.card a は (0,1,1)、a は (0,0,1) で、左の桁から比べます。桁は繰り上がらないので、クラスを何個重ねても ID 1つには勝てません。:where() の中身は詳細度 0、:is() と :not() は中身のうち最も高いものを数えます。5段目まで来て初めて「後に書いた方が勝つ」が効きます。
下の部品で確かめてください。1つのリンク要素に6枚の規則が当たっています。!important・レイヤ・セレクタ・並び順を変えると、勝った規則から要素へ線が引かれ、どの段で決着したかが光ります。同じ規則を本物のブラウザにも当てて、計算された色と突き合わせています。
ID セレクタ #lead の紫が効かず、橙になっています。カードの !important・レイヤ・セレクタ(押すと変わる)・↑↓(並び順)を変えて、紫を勝たせてください。
この順序を知らずに !important で殴り合いを始めると、スタイルシートは急速に壊れていきます。!important は出どころと重要度の段を丸ごと1つ上げてしまうので、それを覆すには次の !important が必要になるからです。実務では「詳細度をなるべく低く・フラットに保つ」のが定石で、BEMのようなクラス命名規則やユーティリティファーストのアプローチ、:where() による詳細度の打ち消し、そしてレイヤによる層分けは、いずれも詳細度の衝突を避けるための工夫です。
ボックスモデルと継承
画面上のすべての要素は長方形の「ボックス」として描かれ、内側から content(内容)→ padding(内余白)→ border(枠線)→ margin(外余白)の層を持ちます。width が content だけの幅を指すのか(既定の content-box)、border までを含むのか(border-box)は、レイアウト崩れの定番の原因で、多くのリセット CSS が全要素を border-box にそろえるのはこのためです。
color や font-family のような文字系のプロパティは親から子へ自動的に継承されるため、body に一度指定すればページ全体に行き渡ります。ここで混同しやすいのが継承とカスケードの関係です。継承は「その要素自身に当たる宣言が1つもないとき」にだけ親の値を使う仕組みで、カスケードの勝負には参加しません。親に !important で色を付けても、子に a { color: blue } のような弱い規則が1つでも当たれば、子は自分の規則の色になります。上の部品でリンクが最初から青いのも、ページの文字色を継承する前に、ブラウザ既定の :link の規則が当たっているからです。
レイアウト——float の時代から Flexbox・Grid へ
長らくCSSには「要素を横に並べる」正攻法がなく、本来は回り込み用の float や <table> を流用したハックが横行していました。現在は一次元の並びを制御する Flexbox と、二次元の格子を定義する Grid が標準となり、かつて何十行も要したレイアウトが数行で書けます。さらにメディアクエリ(@media)で画面幅に応じて規則を切り替えることで、一つのHTMLをPCにもスマートフォンにも適応させるレスポンシブデザインが実現します。最近は、画面ではなく置かれた箱の幅に応じて切り替えるコンテナクエリ(@container)も使えます。
レンダリングとの関係
ブラウザはCSSを解析して CSSOM というツリーを作り、HTML から作られた DOM の各要素に、ここまで見たカスケードでスタイルを確定させてから、レイアウト計算と描画を行います(この流れ全体はブラウザレンダリングで扱います)。<head> で読み込むスタイルシートは、届くまで画面の描画を止めます。スタイルが未確定のまま描くと、あとで一気に見た目が変わってしまうからです。
また、JavaScript から width を変えるとブラウザはレイアウトからやり直し、color を変えれば塗り直しから、transform や opacity なら多くの場合は層を重ね直す合成(コンポジット)だけで済みます。アニメーションで transform や opacity が推奨されるのは、レイアウトと塗り直しを避け、合成を GPU に任せられるためです。どのプロパティがどの工程から再実行を招くかは、パフォーマンスの話題の出発点になります。
現代のCSSとエコシステム
CSSは静的な言語だと思われがちですが、表現力は年々広がっています。カスタムプロパティ(CSS変数)でテーマの一元管理ができ、calc() で動的な計算、ネスト記法や :has() セレクタ(子の状態で親を選べる)など、かつてSassのようなプリプロセッサや JavaScript に頼っていた機能の多くが標準に取り込まれました。一方、大規模開発ではクラス名の衝突やスタイルの影響範囲が問題になり、CSS Modules、CSS-in-JS、Tailwind CSSのようなユーティリティファースト、そして標準の @layer や @scope といった手法が使われています。手法は違えど、いずれも「グローバルに効いてしまうCSSのスコープと優先順位をどう管理するか」という、カスケードが生んだ同じ課題への答えです。
試問
1つのリンク <a id="lead" class="note"> に、色の規則がいくつも当たっています。制作者のスタイルシートでは @layer base, theme; と宣言済みで、ID セレクタ #lead の紫は @layer base の中にあり、いまは a.note の橙が勝っています。規則のカードでは、!important・レイヤ・セレクタ(押すと変わる)・並び順(↑↓)・style 属性を切り替えられ、どの段で勝負がついたかが光ります。
ID セレクタ #lead の紫が効かず、橙になっています。カードの !important・レイヤ・セレクタ(押すと変わる)・↑↓(並び順)を変えて、紫を勝たせてください。
