STEP 2 / CSS

意味のある文書を、
見せたい形にする。

CSS は「どの要素を」「どう見せるか」を書く言語です。昔は苦労した中央揃えも段組みも、いまは Flexbox と Grid で数行。ここ数年で入った新しい書き方まで、ひととおり見ていきます。

目次を開く(全 20 章)

プレビュー付きの例は「⬇ 保存」で HTML と CSS がひとつになったファイルとして保存できます。数字を書き換えて、変化を確かめてください。

01

CSS の書き方

CSS は「どれに(セレクタ)、何を(プロパティ)、どうする(値)」の 3 点セットです。これがひたすら並んでいるだけです。

CSS
h1 {                    /* セレクタ:どの要素に当てるか */
  color: #2f80b8;       /* プロパティ: 値;  ← 最後のセミコロンを忘れずに */
  font-size: 2rem;
}

/* これはコメント。CSS には // 形式のコメントはありません */

HTML への当て方は 3 通りありますが、基本は外部ファイル 1 択です。

  • 外部ファイル(推奨)— <link rel="stylesheet" href="…css">。全ページで共有でき、直す場所が 1 か所になります。
  • <style> タグ — そのページだけの指定。プレビューや実験用。
  • style 属性<p style="color:red">。優先度が高すぎて後から上書きできなくなるので、原則使いません(JavaScript から動的に変えるときは別)。

02

セレクタ

「どれに当てるか」の指定方法です。まずはこれだけ覚えれば十分です。

CSS
p               { }   /* 要素:すべての p */
.card           { }   /* クラス:class="card" 何個あってもよい ← 主役 */
#header         { }   /* id:ページに 1 つだけ。強すぎるので控えめに */
[type="email"]  { }   /* 属性 */

.card p         { }   /* 子孫:card の中にある p すべて */
.card > p       { }   /* 直接の子だけ */
.card + p       { }   /* 直後に並ぶ兄弟 */
.card ~ p       { }   /* 後ろに続く兄弟すべて */

a:hover         { }   /* 状態:マウスが乗っている */
a:focus-visible { }   /* キーボードで選ばれている(← 消してはいけない) */
li:first-child  { }   /* 位置 */
li:nth-child(2n){ }   /* 偶数番目 */
input:checked   { }   /* チェックされている */

.card, .panel   { }   /* まとめて指定(カンマ区切り) */
クラス名の付け方

見た目ではなく役割で名付けます。.red-text ではなく .error-message。あとで色を変えたときに、名前が嘘にならないためです。単語の区切りはハイフン(.site-header)が一般的です。

03

優先順位と継承

同じ要素に複数の指定が当たったとき、どれが勝つか。これが CSS でいちばん最初につまずくところです。ルールは「詳細度 → 書いた順」。詳細度が同じなら、後に書いたほうが勝ちます。

種類強さ
style 属性style="color:red"非常に強い(使わない)
id#header強い
クラス・属性・擬似クラス.card :hoverふつう ← ここで戦う
要素p div弱い
:where() の中身:where(.card)ゼロ(上書きしやすい)
!important に頼らない

!important は全部を飛び越えて勝ちますが、次に上書きしたくなったとき もっと強い !important が必要になるという消耗戦を招きます。効かないときは「詳細度が足りない」のか「順番が逆」なのかを、ブラウザの開発者ツールで確かめてください。 @layer を使うと、この戦い自体を避けられます。

なお colorfont-family などは親から子へ引き継がれます(継承)。 body に一度だけ書けば全体に効くので、同じ指定を繰り返さないで済みます。一方 borderpadding は継承しません。

04

リセット CSS — 土台をそろえる

ブラウザは、何も指定しなくても見出しや段落に独自の余白を付けています。しかもその値はブラウザごとに微妙に違います。 最初にこれを一度そろえてしまうのがリセット CSS です。 CSS ファイルのいちばん上に置きます。

CSS — 最小限のリセット
/* 1. 幅の計算に padding と border を含める */
*, *::before, *::after { box-sizing: border-box; }

/* 2. 既定の余白を消す(必要な余白は自分で付ける) */
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol, pre {
  margin: 0;
}

/* 3. 画像・動画を扱いやすくする */
img, svg, video, canvas {
  display: block;          /* 下にできる謎の隙間を消す */
  max-inline-size: 100%;   /* 親からはみ出さない */
}

/* 4. 本文の土台 */
body {
  min-block-size: 100dvh;
  line-height: 1.7;
  font-family: system-ui, sans-serif;
  -webkit-text-size-adjust: 100%;   /* iPhone の横向きで文字が太らないように */
}

/* 5. フォーム部品は文字の設定を引き継がないので、明示する */
button, input, select, textarea {
  font: inherit;
  color: inherit;
}

/* 6. 記号を消すのは「箇条書きに見せない」ときだけ。
      読み上げソフトにリストだと伝えるため role="list" を付ける */
ul[role="list"], ol[role="list"] {
  list-style: none;
  padding-inline-start: 0;
}
:where() で包むともっと安全

:where(h1, h2, p) { margin: 0; } のように書くと詳細度がゼロになり、あとから書くどんな指定でも上書きできます。リセットは「土台」なので、後から必ず負けるようにしておくのが理想です。 @layer と組み合わせると、さらに確実になります。

先達のリセットを借りる

自分で書かなくても、よくできたものが公開されています。ブラウザ間の差だけを埋める normalize.css、既定をしっかり消してしまう @acab/reset.css(約 2.3KB、ほぼ全部が :where() 包み)など、方針の違うものがいくつもあります。使ってみるのも良いと思います。 どこまで消すかは製品ごとに違うので、一度中身を読んでから採用してください。

既製のリセットを読み込む
/* @layer と組み合わせると、リセットを「いちばん弱い層」に閉じ込められる。
   @layer の宣言だけは @import より前に置けます */
@layer reset, base, components, utilities;
@import url("https://cdn.jsdelivr.net/npm/@acab/reset.css@0.11.0/index.min.css") layer(reset);
借りるときに気をつけること

@0.11.0 のようにバージョンを固定すること。そして CDN からの読み込みはオフラインでは効かないので、公開するときはファイルをダウンロードして同梱するのが確実です(このサイトが CDN を一切使っていないのも同じ理由です)。

05

ボックスモデル

すべての要素は四角い箱です。内側から中身 → padding(内側の余白)→ border(線)→ margin(外側の余白)の順に重なっています。

CSS
.box {
  inline-size: 220px;              /* = width。文字の流れに沿った「横幅」 */
  padding: 14px;
  border: 4px solid #2f80b8;
  margin-block-end: 12px;          /* = margin-bottom */
  background-color: #eaf2f8;
  border-radius: 8px;
}

/* padding と border を幅に含める。これを最初に全体へ書いておく */
.fix {
  box-sizing: border-box;
  background-color: #fff2ec;
  border-color: #ff6a3d;
}

上の箱は 220 + 14×2 + 4×2 = 256px になります。下の箱は指定どおり 220px。だから最初に全要素へ box-sizing: border-box を書いておくのが定番です。

CSS — 最初に書いておくおまじない
*, *::before, *::after { box-sizing: border-box; }
論理プロパティ

width / height / margin-left の代わりに inline-size / block-size / margin-inline-start と書く方法があります。縦書きや右から左に読む言語でも意味が崩れないので、新しく書くならこちらがおすすめです。 top/right/bottom/leftinset-* にまとめられます。

06

色の指定

色の書き方はいくつもありますが、今から覚えるなら 16 進数スペース区切りの rgb()、そしてカスタムプロパティの 3 つで足ります。

CSS
:root {
  /* カスタムプロパティ(変数)。色は必ずここに集める */
  --brand: #2f80b8;
  --accent: #ff6a3d;
}

.row { display: flex; flex-wrap: wrap; gap: 8px; }

.chip {
  padding: 8px 14px;
  border-radius: 999px;
  color: #fff;
  font-size: 13px;
}

.a { background-color: var(--brand); }

/* 今の書き方はカンマなし。透明度は / のあとに書く */
.b { background-color: rgb(58 167 109); }
.c { background-color: rgb(255 106 61 / 45%); color: #14202b; }

/* 2 色を混ぜる。濃淡のバリエーションを手で計算しなくてよい */
.d { background-color: color-mix(in srgb, var(--brand) 50%, var(--accent)); }

.lead-text { color: var(--accent); }

.dot {
  display: inline-block;
  inline-size: 14px;
  block-size: 14px;
  border-radius: 50%;
  background-color: currentColor;   /* 文字色をそのまま使う */
  vertical-align: -2px;
}
  • カスタムプロパティ--名前: 値; で定義し、var(--名前) で使います。:root に書けばページ全体で使えます。
  • 古い rgba(255, 106, 61, 0.45) も動きますが、今は rgb(255 106 61 / 45%) に統一するとすっきりします。
  • hsl(210 60% 45%) は「色相・鮮やかさ・明るさ」で指定する書き方。明度だけ変えた色を作りやすいのが利点です。
  • 文字色と背景色のコントラストは必ず確認を。薄いグレーの文字は読めません。開発者ツールで比率を測れます。

07

配色を決める

色は増やすほど画面が散らかります。最初は主役・補助・差し色・文字の 4 つだけ決め、名前は「青」ではなく役割で付けます(あとで色を変えても名前が嘘になりません)。使う面積の目安は昔から 70 : 25 : 5 と言われます。

  • 地色 … 約 70% — 白や --sub-color。いちばん広く占める背景です
  • --main-color … 約 25% — 見出し、ボタン、リンク、罫線
  • --accent-color … 約 5% — いちばん押してほしい 1 か所だけ

注意したいのは、70% は「地色」であり主役の色ではないことです。主役を 70% 使うと画面が塗りつぶされ、かえって主役が目立たなくなります。なお --text-color は面積比とは別枠で、読みやすさ(背景とのコントラスト)で決めます。

そして、ホバーで少し濃く・無効状態で薄くといった派生色はcolor-mix() を使うと便利です。主役の色を変えたときには濃淡も自動で決まります。混色方法は in srgb より in oklab のほうが、明るさの変化が目で見て自然になります。

CSS
:root {
  --main-color: #2f80b8;
  --sub-color: #eaf2f8;
  --accent-color: #ff6a3d;
  --text-color: #14202b;
}

.swatches { display: flex; flex-wrap: wrap; gap: 8px; }
.sw { padding: 10px 16px; border-radius: 8px; color: #fff; font-size: 12px; }
.main { background-color: var(--main-color); }
.sub { background-color: var(--sub-color); color: var(--text-color); }
.accent { background-color: var(--accent-color); }
.text { background-color: var(--text-color); }

.button {
  padding: 9px 18px;
  border: 0;
  border-radius: 999px;
  background-color: var(--main-color);
  color: #fff;
  font: inherit;
  cursor: pointer;
  transition: background-color .2s;
}

/* 黒を 18% 混ぜて、少しだけ濃くする */
.button:hover { background-color: color-mix(in oklab, var(--main-color) 82%, black); }

/* 押した瞬間はもう少し濃く */
.button:active { background-color: color-mix(in oklab, var(--main-color) 70%, black); }

/* 白を混ぜれば薄くなる */
.button:disabled {
  background-color: color-mix(in oklab, var(--main-color) 40%, white);
  cursor: not-allowed;
}

/* transparent を混ぜると半透明も作れる */
.panel {
  margin-block-start: 12px;
  padding: 12px;
  border-radius: 8px;
  background-color: color-mix(in srgb, var(--main-color) 12%, transparent);
  color: var(--text-color);
}

--main-color の 1 行を別の色に書き換えて「↻ 再表示」を押すと、ホバー時も無効時も背景パネルも、まとめて追随します。

濃淡を変数に入れるときの注意

--main-dark のように変数へ入れておくと、その値は宣言した場所(:root)で確定します。子要素で --main-color だけ差し替えても追随しません。テーマごとに色を変えるなら濃淡も同じ場所で定義し直すか、上の例のように使う場所で直接混ぜてください。

08

グラデーション

グラデーションは「色」ではなく画像として扱われます。だから background-image に指定し、カンマ区切りで何枚でも重ねられます(先に書いたものが上)。写真の上に文字を載せるときの定番手段でもあります。

CSS
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); gap: 8px; }

.g {
  display: grid;
  place-items: center;
  block-size: 70px;
  border-radius: 8px;
  color: #fff;
  font-size: 12px;
  text-shadow: 0 1px 3px rgb(0 0 0 / 45%);
}

/* 直線状。角度 → 色 → 色。位置(%)で切り替え点を指定できる */
.linear { background-image: linear-gradient(120deg, #1b4e77, #2f80b8 60%, #ff6a3d); }

/* 放射状。中心の位置も指定できる */
.radial { background-image: radial-gradient(circle at 30% 30%, #ffffff, #2f80b8); }

/* 円錐状。円グラフや色相環に使える */
.conic {
  background-image: conic-gradient(
    #2f80b8 0turn 0.25turn,
    #9dc6e2 0.25turn 0.5turn,
    #ff6a3d 0.5turn 0.75turn,
    #ffd9cd 0.75turn 1turn);
}

/* 繰り返すとストライプになる */
.stripe {
  background-image: repeating-linear-gradient(45deg, #1b4e77 0 8px, #2f80b8 8px 16px);
}

/* 写真の上に「暗くする層」を重ねる。文字が確実に読めるようになる */
.overlay {
  display: grid;
  place-items: center;
  block-size: 90px;
  margin-block-start: 10px;
  border-radius: 10px;
  background-image:
    linear-gradient(rgb(13 35 56 / 72%), rgb(13 35 56 / 30%)),
    linear-gradient(120deg, #ffcc00, #3aa76d);   /* ← ここが本来は写真 */
  background-size: cover;
}

.overlay span { color: #fff; font-weight: 700; }

/* 文字だけをグラデーションで塗る */
.title {
  margin-block-start: 10px;
  font-size: 20px;
  font-weight: 700;
  background-image: linear-gradient(90deg, #2f80b8, #ff6a3d);
  background-clip: text;
  color: transparent;
}

色の切り替え点は #2f80b8 60% のように「色 位置」で書きます。位置を 2 つ書く(#1b4e77 0 8px)と、その区間はべた塗りになり、境目のはっきりした縞になります。

09

線と影と角丸

borderbox-shadow は、今でも毎日使う道具です。「浮いている」「囲まれている」という手がかりは、平らなデザインが主流の今こそ効きます。一方 text-shadow は出番が減りましたが、写真の上に文字を置くときには今も有効です。

CSS
.card {
  /* 太さ 種類 色 の順。色キーワードは red / blue のようにそのまま書く
     (#red という書き方はありません。16 進数なら #f00) */
  border: 1px solid #cfdde8;
  border-radius: 12px;
  padding: 14px;
  margin-block-end: 10px;

  /* x方向 y方向 ぼかし 広がり 色。
     影の色は黒ではなく「濃い青を薄めたもの」にすると自然に見える */
  box-shadow: 0 6px 18px -10px rgb(13 35 56 / 45%);
}

.float {
  box-shadow: 0 14px 30px -14px rgb(13 35 56 / 55%);
  border-color: transparent;
}

.dashed {
  border: 2px dashed #9dc6e2;   /* solid / dashed / dotted / none */
  box-shadow: none;
}

/* 一辺だけ、角ひとつだけ、も指定できる */
.card.dashed { border-inline-start-width: 6px; border-start-start-radius: 0; }

.photo {
  display: grid;
  place-items: center;
  block-size: 90px;
  border-radius: 12px;
  background-image: linear-gradient(120deg, #1b4e77, #2f80b8 60%, #ff6a3d);
}

.photo span {
  color: #fff;
  font-weight: 700;
  /* 背景が明るくても文字が読めるように、うっすら影を敷く */
  text-shadow: 0 2px 6px rgb(0 0 0 / 55%);
}

/* フォーカスの枠。box-shadow でリングを作る書き方もよく使う */
.focus-demo {
  padding: 8px 16px;
  border: 1px solid #cfdde8;
  border-radius: 999px;
  background-color: #fff;
}

.focus-demo:focus-visible {
  outline: 2px solid transparent;   /* Windows のハイコントラスト対策 */
  box-shadow: 0 0 0 3px rgb(255 106 61 / 55%);
}
  • box-shadow は現役です。カードの浮き上がり、押したときの沈み込み、フォーカスリング。カンマ区切りでいくつも重ねられます
  • 影は薄く、広く。0 8px 24px -12px のように 4 つめ(広がり)を負の値にすると、輪郭がぼやけて自然になります。真っ黒な影は安っぽく見えます。
  • text-shadow は写真の上だけ。本文に使うと読みにくくなります。写真の上なら、影より暗いグラデーションを 1 枚重ねるほうが確実です(次章)。
  • outline は消さない。キーボード操作の人が現在地を見失います。デザインが気に入らないなら、:focus-visible で自分好みの枠に置き換えてください。
  • border-radius: 50% で円、999px で角丸のカプセル形。個別なら border-start-start-radius などで 1 か所ずつ。

10

文字と単位

本文の読みやすさは、文字サイズ・行間・1 行の長さでほぼ決まります。凝った装飾より先にここを整えます。

CSS
body {
  font-family: "Hiragino Sans", "Noto Sans JP", system-ui, sans-serif;
  line-height: 1.8;            /* 単位なしで指定するのが基本 */
  color: #14202b;
}

h2 {
  /* 画面幅に応じて 1.2rem 〜 1.9rem の間でなめらかに変わる */
  font-size: clamp(1.2rem, 1rem + 2vw, 1.9rem);
  line-height: 1.35;
  text-wrap: balance;          /* 見出しの折り返しを整える */
}

p {
  max-inline-size: 34em;       /* 1 行が長くなりすぎないように */
  text-wrap: pretty;           /* 最終行に 1 文字だけ残さない */
}

単位の使い分け

単位意味主な使いどころ
remルートの文字サイズ基準(既定 16px)文字サイズ・余白。まずこれ
emその要素の文字サイズ基準文字に連動させたい余白
px画面のドット線幅・角丸など、拡大したくないもの
%親要素に対する割合
vw / dvh画面幅・画面高さの 1%全画面の見出しなど。dvh はスマホのアドレスバー対応
ch文字「0」の幅1 行の文字数を決めたいとき
fr余った空間の分け前Grid 専用
clamp() が便利

clamp(最小, 可変, 最大) と書くと、画面幅に応じてなめらかに変わり、小さくなりすぎも大きくなりすぎもしません。メディアクエリで文字サイズを何段階も刻む必要がなくなります。

11

Flexbox — 一列に並べる

1 方向に並べるならこれ。ヘッダーの左右振り分け、ボタンの横並び、カードの列。 display: flexgap を覚えるだけで、Web の 8 割のレイアウトが片付きます。

CSS
.bar {
  display: flex;
  align-items: center;             /* 縦方向の位置をそろえる */
  justify-content: space-between;  /* 左右の端に寄せる */
  gap: 12px;
  padding: 10px 14px;
  background-color: #eaf2f8;
  border-radius: 8px;
}

.menu {
  display: flex;
  flex-wrap: wrap;                 /* 入りきらなければ折り返す */
  gap: 4px 14px;
}

.menu a { color: #1b4e77; }

.cards {
  display: flex;
  gap: 10px;
  margin-block-start: 12px;
}

.card {
  flex: 1;                         /* 余った幅を等しく分け合う */
  padding: 16px 12px;
  background-color: #2f80b8;
  color: #fff;
  border-radius: 8px;
  text-align: center;
}

.wide { flex: 2; }                 /* こちらは 2 倍もらう */

すき間は gap で空ける

並べたものの間隔は、margin ではなく gap で指定します。gap要素と要素の間だけに効くので、いちばん外側に余分な余白が残りません。margin で空けていた頃は、最後のひとつだけ :last-child で打ち消す、といった後始末が必要でした。

CSS
/* すき間は 1 か所で決める。両端には余白が付かない */
.row {
  display: flex;
  gap: 16px;
}

/* 値を 2 つ書くと「行の間 列の間」の順になる */
.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
}

/* 個別に書きたいときは row-gap / column-gap */
.list {
  display: flex;
  row-gap: 8px;
  column-gap: 24px;
}

gap が効くのは flexgrid・段組みの中だけです。ふつうに縦に積んだブロックの間隔は、これまでどおり margin で空けます。

折り返す並びで幅を固定するとき

flex-wrap: wrapgap を組み合わせると、すき間の分だけ子要素の置ける幅が減りますflex: 1 は残りを分け合うだけなので気になりませんが、width: 50% のように固定で書くと合計が幅を超えて 2 つ目が折り返してしまいます。calc(50% - 8px) のように引くか、次に出てくる Grid の 1fr に任せてください。

display: flex
直接の子要素を並べる。既定は横方向。
flex-direction
row(横)か column(縦)。
gap
要素の間隔。margin で調整する時代は終わりました。
justify-content
並びの方向の配置。flex-start / center / space-between
align-items
交差方向(横並びなら縦)の配置。center で高さがそろいます。
flex-wrap
wrap で折り返す。狭い画面対策の基本。
flex: 1
余白の分け前。数字が大きいほど多くもらいます。

12

CSS Grid — 格子に並べる

縦と横を同時に決めたいときは Grid。カード一覧やページ全体の段組みが得意です。メディアクエリを書かずに折り返す書き方から覚えると、すぐ役に立ちます。

CSS
/* 「90px 以上を保ちつつ、入るだけ並べて余りは等分」
   これだけで、幅に応じて 2 列にも 4 列にもなる */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
  gap: 8px;
}

.cell {
  padding: 18px 0;
  background-color: #9dc6e2;
  border-radius: 6px;
  text-align: center;
}

/* 場所に名前を付けて配置する書き方 */
.page {
  display: grid;
  grid-template-columns: 1fr 2fr;
  grid-template-areas:
    "side main"
    "foot foot";
  gap: 8px;
  margin-block-start: 12px;
}

.side { grid-area: side; background-color: #ffd9cd; }
.main { grid-area: main; background-color: #ff6a3d; color: #fff; }
.foot { grid-area: foot; background-color: #0d2338; color: #fff; }

.page > * {
  padding: 14px;
  border-radius: 6px;
  text-align: center;
}

ブラウザの幅を変えると、上の 6 個が自動で折り返します。auto-fillauto-fit にすると、要素が少ないときに横いっぱいまで伸びます。

Grid の gap

書き方は Flex とまったく同じで、gap ひとつで縦横のすき間が決まります。ちがうのは 1fr との関係です。Grid はすき間を先に取り分けてから、残りを 1fr で分けるので、gap をいくら広げても列がはみ出しません。幅を % で計算していた頃といちばん違うところです。

CSS
/* 3 列 + すき間 16px。1fr は「すき間を引いた残り」を 3 等分するので、
   合計が親の幅を超えることがない */
.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

/* 縦と横で変えるなら「行の間 列の間」の順 */
.photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 24px 16px;
}
Flex と Grid の使い分け

並べたいものが 1 方向なら Flex、行と列の両方を決めたいなら Grid。 中身の大きさに任せたいなら Flex、外側から枠を決めたいなら Grid、とも言えます。両方を入れ子で使うのがふつうです。

13

配置と重なり

通常の要素は上から順に積まれます(これを「通常フロー」と呼びます)。そこから外して自由に置きたいときだけ position を使います。

CSS
.scroller {
  block-size: 190px;
  overflow: auto;
  padding: 0 12px 12px;
  border: 1px solid #cfdde8;
  border-radius: 8px;
}

.sticky {
  position: sticky;            /* 通常フローのまま、端で止まる */
  inset-block-start: 0;        /* = top: 0 */
  margin: 0;
  padding: 10px 0;
  background-color: #fff;
  border-block-end: 2px solid #2f80b8;
  font-size: 15px;
}

.badge-box {
  position: relative;          /* 中の absolute の基準になる */
  margin-block-start: 12px;
  padding: 18px 14px;
  background-color: #eaf2f8;
  border-radius: 8px;
}

.badge {
  position: absolute;          /* 基準からの位置で置く */
  inset-block-start: -8px;
  inset-inline-end: -6px;
  padding: 3px 10px;
  background-color: #ff6a3d;
  color: #fff;
  font-size: 11px;
  border-radius: 999px;
}
static
既定。通常フローのまま。
relative
元の位置を保ったまま少しずらせる。absolute の基準にもなる。
absolute
フローから外れ、いちばん近い relative な祖先を基準に置かれる。
fixed
画面に固定。スクロールしても動かない。
sticky
ふだんは通常フロー、指定位置まで来ると貼り付く。inset-block-start の指定が必須。
z-index
重なりの順番。positionstatic 以外の要素にだけ効きます。

14

:is() :where() :has()

セレクタを短く書ける :is()、詳細度をゼロにする :where()、そして「中身によって親の見た目を変える」という長年できなかったことを可能にした :has()。この 3 つはどのブラウザでも使えます。

CSS
/* :is() — 長い列挙をまとめる。詳細度は中身のいちばん強いものになる */
:is(h1, h2, h3) + p { margin-block-start: .5em; }

/* 昔はこう書いていた */
.article h1 + p, .article h2 + p, .article h3 + p { }

/* :where() — 同じくまとめるが、詳細度は 0。
   「あとから簡単に上書きできる土台」を作るのに向く */
:where(ul, ol) { padding-inline-start: 1.2em; }

/* :has() — 中に◯◯を持つ親、を選べる(親セレクタ) */
.card:has(img)          { padding: 0; }          /* 画像入りのカードだけ */
label:has(:checked)     { background-color: #e6f4ec; }
form:has(:invalid) .send { opacity: .5; }        /* 未入力なら送信を薄く */
CSS
.field {
  display: block;
  padding: 10px 12px;
  border: 1px solid #cfdde8;
  border-radius: 8px;
  transition: background-color .2s;
}

/* チェックされた input を「持つ」label の色を変える */
.field:has(:checked) {
  background-color: #e6f4ec;
  border-color: #3aa76d;
  color: #1b6b45;
}

.card {
  position: relative;
  margin-block-start: 10px;
  padding: 12px 14px;
  border: 2px solid #cfdde8;
  border-radius: 8px;
}

.card h3 { margin: 0 0 4px; font-size: 15px; }
.card p { margin: 0; font-size: 13px; }

/* .badge を持つカードだけ色を変える */
.card:has(.badge) {
  border-color: #ff6a3d;
  background-color: #fff5f1;
}

.badge {
  position: absolute;
  inset-block-start: 10px;
  inset-inline-end: 12px;
  padding: 2px 8px;
  background-color: #ff6a3d;
  color: #fff;
  font-size: 11px;
  border-radius: 999px;
}

チェックボックスを押してみてください。JavaScript を 1 行も書かずに、親要素の見た目が変わります。

15

@layer とネスト記法

CSS が育ってくると「なぜか上書きできない」が必ず起きます。 @layer優先順位をファイルの先頭で宣言してしまう仕組みで、この問題を根本から避けられます。

CSS — @layer
/* 先に順番を宣言する。後ろの層ほど強い */
@layer reset, base, components, utilities;

@layer reset {
  /* :where() で詳細度 0 にしておくと、あとで何でも上書きできる */
  :where(h1, h2, p) { margin: 0; }
}

@layer components {
  /* id セレクタでも、utilities 層には勝てない */
  #main .button { background-color: #2f80b8; }
}

@layer utilities {
  .is-danger { background-color: #c0392b; }   /* こちらが勝つ */
}

層の順番が詳細度より優先されます。 だから「コンポーネントの指定が、あとから足したユーティリティに勝ってしまう」といった事故が起きません。

ネスト記法

セレクタの繰り返しを書かずに済む書き方です。Sass のような別ツールを入れなくても、素の CSS で使えます。 子孫セレクタを書くだけなら & は要りません。 &(自分自身を指す記号)が必要なのは、次の 3 つの場合だけです。

  • 擬似クラス・擬似要素を付ける&:hover &::before
  • 自分自身にクラスや属性を足す&.is-open &[disabled]
  • 親を後ろに置きたい.sidebar &(= .sidebar .card
CSS — ネスト
.card {
  padding: 16px;                  /* 宣言はネストしたルールより先に書く */
  border-radius: 12px;

  h3 { font-size: 1.1rem; }       /* .card h3 と同じ。& は書かなくてよい */

  p + p { margin-block-start: .5em; }

  &:hover { translate: 0 -4px; }        /* .card:hover */
  &.is-open { border-color: #ff6a3d; }  /* .card.is-open */

  .sidebar & { padding: 12px; }   /* .sidebar .card(親を後ろに置く) */

  @media (width >= 40rem) {       /* メディアクエリも中に書ける */
    padding: 24px;
  }
}
& が付いたコードをよく見かける理由

ネストが最初に実装されたころは、ネストしたセレクタの先頭に & か記号が必須でした。 h3 { } のように要素名で始められるようになったのは、そのあと緩和されてからです。そのため既存のコードには & 付きが多く残っています。動作は同じなので、読むときは「これは省略できるほうだな」と思って構いません。

ネストの落とし穴

プロパティの宣言は、ネストしたルールより前に書いてください。 後ろに書くとブラウザによって解釈が揺れます。また、入れ子は深くしすぎないこと。3 段を超えると、どの要素の話か分からなくなります。

16

レスポンシブ — 画面幅と、要素の幅

スマホから大画面まで、1 つの CSS で対応します。基準は「狭い画面の指定を先に書き、広くなったら足す」(モバイルファースト)。こうすると打ち消しの指定が減ります。

CSS — メディアクエリ(レンジ構文)
/* まず狭い画面の指定を書く */
.cards { display: grid; gap: 12px; }

/* 60rem(=960px)以上のときだけ 3 列にする */
@media (width >= 60rem) {
  .cards { grid-template-columns: repeat(3, 1fr); }
}

/* 昔の書き方(今も動く)
@media (min-width: 960px) { }
*/

/* 範囲もそのまま書ける */
@media (40rem <= width < 60rem) { }

/* 画面幅以外にも聞ける */
@media (prefers-reduced-motion: reduce) { }   /* 動きを減らす設定 */
@media (prefers-color-scheme: dark) { }       /* ダークモード */
@media (pointer: coarse) { }                  /* 指で操作している */
@media print { }                              /* 印刷時 */

コンテナクエリ — 「置かれた場所の幅」で変える

同じカードでも、サイドバーに置けば狭く、メインに置けば広い。画面幅ではなく親の幅で切り替えられるのがコンテナクエリです。部品を「どこに置いても正しく見える」ようにできます。

CSS
.holder {
  container-type: inline-size;   /* この要素を「幅の基準」にする */
  container-name: card;
  resize: horizontal;            /* 手でつまんで幅を変えられるように */
  overflow: auto;
  min-inline-size: 180px;
  max-inline-size: 100%;
  padding: 10px;
  border: 2px dashed #9dc6e2;
  border-radius: 10px;
}

.card {
  display: grid;
  gap: 8px;
  padding: 12px;
  background-color: #eaf2f8;
  border-radius: 8px;
}

.tag {
  justify-self: start;
  padding: 2px 10px;
  background-color: #2f80b8;
  color: #fff;
  font-size: 11px;
  border-radius: 999px;
}

.card h3 { margin: 0; font-size: 15px; }
.card p { margin: 0; font-size: 13px; }

/* 基準にした .holder の幅が 24rem 以上になったら、
   その「子孫」である .card の並びを変える */
@container card (width >= 24rem) {
  .card { grid-template-columns: auto 1fr; align-items: center; }
}

.hint { margin: 0 0 8px; font-size: 12px; color: #5b6f80; }
コンテナクエリのつまずき

container-type を付けた要素自身のスタイルは、その @container では変えられません(自分の幅で自分を変えると堂々巡りになるため)。変えられるのは子孫だけです。だから上の例では、外側に .holder を 1 枚かぶせています。

17

ダークモード

色をすべてカスタムプロパティにまとめておけば、ダークモード対応は「変数を差し替えるだけ」で済みます。個別の場所で色を直接書いていると、ここで必ず苦労します。

CSS
:root {
  /* フォームやスクロールバーもまとめて暗くしてくれる */
  color-scheme: light dark;

  --bg: #ffffff;
  --text: #14202b;
  --line: rgb(13 35 56 / 14%);
}

/* OS がダークのとき(ユーザーが手動で light を選んでいなければ) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #101820;
    --text: #e6eef5;
    --line: rgb(157 198 226 / 18%);
  }
}

/* ページ上のボタンで明示的にダークを選んだとき */
:root[data-theme="dark"] {
  --bg: #101820;
  --text: #e6eef5;
  --line: rgb(157 198 226 / 18%);
}

body {
  background-color: var(--bg);
  color: var(--text);
}
3 つの状態を考える

「OS 設定に従う(既定)」「手動でライト」「手動でダーク」の 3 通りがあります。上のように :not([data-theme="light"]) を挟むと、 手動の選択が OS 設定に必ず勝つようになります。このページの右上のボタンも、まさにこの仕組みで動いています。

18

動きをつける

状態が変わるときになめらかにするのが transition放っておいても動き続けるのが animation です。動かすのは opacitytransform 系にとどめると、なめらかに動きます。

CSS
.button {
  padding: 10px 20px;
  border: 0;
  border-radius: 999px;
  background-color: #2f80b8;
  color: #fff;
  font: inherit;
  cursor: pointer;

  /* 変化にかける時間と加減速 */
  transition: background-color .25s ease, translate .25s ease;
}

.button:hover {
  background-color: #ff6a3d;
  translate: 0 -3px;              /* transform: translateY(-3px) と同じ */
}

/* キーボードで選ばれたときの枠は消さない */
.button:focus-visible {
  outline: 3px solid #ff6a3d;
  outline-offset: 3px;
}

@keyframes blink {
  from { opacity: 1; }
  to   { opacity: .2; }
}

.dot {
  display: inline-block;
  inline-size: 10px;
  block-size: 10px;
  border-radius: 50%;
  background-color: #3aa76d;
  animation: blink 1s ease-in-out infinite alternate;
}

/* 「動きを減らす」設定の人には動かさない。必ず入れる */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

19

早見表:知っておくと助かるもの

プロパティできること
aspect-ratio: 16 / 9縦横比を保つ。画像や動画の枠に
object-fit: cover画像を歪めずに枠いっぱいに収める
gapFlex / Grid の間隔。margin より圧倒的に楽
min() max() clamp()計算して値を決める。min(100% - 2rem, 1100px) が定番
overflow-wrap: anywhere長い URL がはみ出すのを防ぐ
text-wrap: balance / pretty見出しと本文の折り返しを整える
scroll-margin-block-start固定ヘッダーの下にアンカーが隠れるのを防ぐ
accent-colorチェックボックスやスライダーの色をブランド色に
:focus-visibleキーボード操作のときだけ枠を出す。消さない
filter / backdrop-filterぼかしや明度調整。背景をすりガラスに
translate rotate scaletransform を個別に書ける新しい書き方
place-items: centerGrid の中央寄せがこれ 1 行で終わる
@supportsその機能が使えるかを聞いてから適用する
content-visibility: auto画面外の描画を後回しにして表示を速く
中央揃えの決まり文句

横方向:inline-size: min(100% - 2rem, 1100px); margin-inline: auto;
上下左右:display: grid; place-items: center;
この 2 つを覚えておけば、中央揃えで悩むことはもうありません。

20

次は JavaScript へ

ここまでで、静かなページなら作れます。 :has()<details> のおかげで、 昔は JavaScript が必要だったことの多くが CSS と HTML だけで済むようになりました。それでも足りないところ ── 計算する、データを取ってくる、状態を覚えておく ── を JavaScript が担当します。

STEP 3 / JavaScript ページに動きと処理を足す 変数・制御構文・関数・クラス・DOM 操作・イベント・非同期処理まで。
実行結果がその場に出るので、書きながら確かめられます。