目次を開く(全 20 章)
01学習レベル:基礎
CSS の書き方
CSS は「どれに(セレクタ)、何を(プロパティ)、どうする(値)」の 3 点セットです。これがひたすら並んでいるだけです。
h1 { /* セレクタ:どの要素に当てるか */
color: #2f80b8; /* プロパティ: 値; ← 最後のセミコロンを忘れずに */
font-size: 2rem;
}
/* これはコメント。CSS には // 形式のコメントはありません */
HTML への当て方は 3 通りありますが、基本は外部ファイルです。
- 外部ファイル(推奨)—
<link rel="stylesheet" href="…css">。全ページで共有でき、直す場所が 1 か所になります。 - <style> タグ — そのページだけの指定。プレビューや実験用。
- style 属性 —
<p style="color:red">。詳細度が高く、CSS ファイル側から上書きしにくくなるので、原則使いません(JavaScript から動的に変えるときは別)。
02学習レベル:基礎
セレクタ
「どれに当てるか」の指定方法です。まずはこれだけ押さえれば十分です。
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 が必要になるという消耗戦を招きます。効かないときは「詳細度が足りない」のか「順番が逆」なのかを、ブラウザの開発者ツールで確かめてください。
@layer を使うと、この戦い自体を避けられます。
なお color や font-family などは親から子へ引き継がれます(継承)。
body に一度だけ書けば全体に効くので、同じ指定を繰り返さないで済みます。一方 border や padding は継承しません。
04学習レベル:基礎
リセット 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(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(外側の余白)の順に重なっています。
.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 を書いておくのが定番です。
*, *::before, *::after { box-sizing: border-box; }
width / height / margin-left の代わりに
inline-size / block-size / margin-inline-start と書く方法があります。縦書きや右から左に読む言語でも意味が崩れないので、新しく書くならこちらがおすすめです。
top/right/bottom/left は inset-* にまとめられます。
ブロックとインライン — display
要素には、並び方の種類があります。見出し・段落・div は、前後で改行して親の横幅いっぱいに広がるブロックです。span・a・strong は、文の流れの中に文字と同じように並ぶインラインです。この種類を決めているのが display プロパティです。
p もブロック。前後で改行される
文の中の span や a はインライン。文字と同じ行に並びます。
inline-blockinline-blockinline-block
.block {
margin: 0 0 8px;
padding: 8px;
border: 2px solid #2f80b8;
background-color: #eaf2f8;
}
.inline {
padding: 2px 6px;
border: 2px solid #ff6a3d;
background-color: #fff2ec;
}
/* 文字と同じ行に並べたまま、幅や上下の余白を指定したいときは inline-block にする */
.tag {
display: inline-block;
inline-size: 110px;
margin: 4px;
text-align: center;
}
下の 3 つは inline-block です。inline-size: 110px で幅がそろい、上下の margin も効いています。同じ指定を span のまま(インラインのまま)書いても、幅は変わりません。
display | 並び方 | 幅と高さ | 既定でこの種類の要素 |
|---|---|---|---|
block | 前後で改行し、親の横幅いっぱいに広がる | 指定できる | div p h1〜h6 ul section |
inline | 文字と同じ行に並ぶ。幅は中身のぶんだけ | 指定しても効かない。上下の margin も効かない | span a strong em |
inline-block | 文字と同じ行に並ぶ | 指定できる | (自分で指定して使う) |
none | 表示せず、場所も取らない | ― | (隠したい要素に) |
- インラインの上下の余白 …
paddingとborderは描かれますが、上下の行を押し広げません。上下の間隔を取りたいなら、inline-blockかブロックにします。 - 画像は文字と同じ行に並ぶ …
imgも既定ではinlineです。ただしspanと違い、幅と高さは指定できます(画像のように、中身を外から読み込んで表示する要素は、こう扱われます)。文字のベースラインにそろえて置かれるので、下に小さな隙間ができます。04 章のリセットでdisplay: blockにしているのは、この隙間を消すためです。 display: noneと読み上げ … 画面から消えるだけでなく、読み上げソフトからも見えなくなります。画面にだけ出さず読み上げには残したいときは、別の書き方(視覚的に隠すクラス)を使います。flexとgrid…displayには、中身の並べ方を決める値もあります。11 章と 12 章で扱います。
内側と外側の余白 — padding と margin
padding は枠線の内側の余白で、背景色もそこまで塗られます。margin は枠線の外側の余白で、隣の要素との距離になります。背景色は塗られません。「中身と枠のあいだを空けたい」なら padding、「となりの箱から離したい」なら margin です。
余白なし
padding:背景ごと内側が広がる
margin:外側が空く。背景は広がらない
margin-inline: auto で中央へ
下に margin 24px
上に margin 16px
/* 外側の点線は、margin の広さを見るための枠 */
.frame {
margin-block-end: 12px;
border: 1px dashed #9dc6e2;
}
.item {
margin: 0;
border: 2px solid #2f80b8;
background-color: #eaf2f8;
}
/* 値 1 つ:上下左右すべて */
.padded { padding: 16px; }
/* 値 2 つ:上下 左右 */
.spaced { margin: 16px 32px; }
/* 幅を決めて、左右の margin を auto にすると中央に寄る */
.centered {
inline-size: 60%;
margin-block-end: 12px;
margin-inline: auto;
text-align: center;
}
/* 上下に並んだ margin は足し算されず、大きいほうだけが残る */
.before { margin-block-end: 24px; }
.after { margin-block-start: 16px; }
最後の 2 つの箱の間は 24 + 16 = 40px ではなく、大きいほうの 24px です。これを「margin の相殺」と呼びます。padding や左右の margin では起きません。
値の個数によって、どの辺に効くかが変わります。4 つ書くときは上から時計回りと覚えます。
| 書き方 | 意味 |
|---|---|
padding: 16px; | 上下左右すべて 16px |
padding: 8px 16px; | 上下 8px、左右 16px |
padding: 8px 16px 24px; | 上 8px、左右 16px、下 24px |
padding: 8px 16px 24px 32px; | 上 8px、右 16px、下 24px、左 32px(時計回り) |
padding-block: 8px; | 上下だけ 8px(論理プロパティ) |
padding-inline: 16px; | 左右だけ 16px(論理プロパティ) |
margin も同じ規則です。1 辺だけなら margin-block-start(上)や padding-inline-end(右)のように書きます。
カードやボタンを並べるとき、1 つずつに margin を付けると、端の要素にも余白が付いてしまいます。親を Grid や Flexbox にして gap を使うと、要素の間だけが空きます。margin は「この要素だけ離したい」ときに使います。
縦横比をそろえる — aspect-ratio と object-fit
写真を並べると、縦長・横長が混ざって高さがそろいません。aspect-ratio で枠の縦横比を決め、object-fit で枠への収め方を決めると、一覧がきれいに並びます。
.thumbs {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
figure { margin: 0; }
.thumbs img {
display: block;
inline-size: 100%;
aspect-ratio: 1 / 1; /* 正方形の枠。高さは幅から自動で決まる */
border-radius: 8px;
background-color: #eaf2f8;
}
.fill { object-fit: fill; } /* 枠に合わせて引き伸ばす(ゆがむ) */
.cover { object-fit: cover; } /* 枠いっぱいに広げ、はみ出た分を切る */
.contain { object-fit: contain; } /* 全体を収め、余りは余白になる */
figcaption {
font-size: 12px;
text-align: center;
}
/* 画像以外の箱にも使える。動画や地図の枠に */
.video {
display: grid;
place-items: center;
inline-size: 240px;
aspect-ratio: 16 / 9;
margin-block-start: 10px;
border-radius: 8px;
background-color: #1b4e77;
color: #fff;
}
横長の図を正方形の枠に入れています。fill では円がつぶれ、cover では左右が切れ、contain では上下に余白ができます。写真の一覧には cover が定番です。切り取る位置は object-position: center top; のように指定できます。
06学習レベル:基礎
色の指定
色の書き方はいくつもありますが、今から覚えるなら
16 進数とスペース区切りの rgb()、そしてカスタムプロパティの 3 つで足ります。
光とインクの三原色
画面は赤・緑・青の光を足す加法混色です。白い紙への印刷は、シアン・マゼンタ・イエローのインクが反射する光を減らす減法混色として考えます。
RGB の各原色と、その光を吸収する CMY の原色は、色相環で向かい合う補色の組です。
| 光の原色 | 対になるインクの原色 | インクが吸収する光 |
|---|---|---|
赤 #ff0000 | シアン #00ffff | 赤 |
緑 #00ff00 | マゼンタ #ff00ff | 緑 |
青 #0000ff | イエロー #ffff00 | 青 |
ここでの「光を足す」は、下の color-mix() で 2 色の中間を作る操作とは別です。
currentColor は文字色をそのまま使えます
: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 のほうが、明るさの変化が目で見て自然になります。
主役の色を 12% だけ混ぜた背景
: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 に指定し、カンマ区切りで何枚でも重ねられます(先に書いたものが上)。写真の上に文字を載せるときの定番手段でもあります。
文字そのものを塗ることもできます
.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)と、その区間はべた塗りになり、境目のはっきりした縞になります。
写真に重ねたグラデーション background-image linear-gradient() radial-gradient() conic-gradient() repeating-linear-gradient() background-clip
09学習レベル:実践
線と影と角丸
border と box-shadow は、今でも毎日使う道具です。「浮いている」「囲まれている」という手がかりは、平らなデザインが主流の今こそ効きます。一方 text-shadow は出番が減りましたが、写真の上に文字を置くときには今も有効です。
.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 枚重ねるほうが確実です(08 章)。outlineは消さない。キーボード操作の人が現在地を見失います。デザインが気に入らないなら、:focus-visibleで自分好みの枠に置き換えてください。border-radius: 50%で円、999pxで角丸のカプセル形。個別ならborder-start-start-radiusなどで 1 か所ずつ。
border border-radius box-shadow text-shadow outline
ぼかしとすりガラス — filter と backdrop-filter
filter は要素そのものを、backdrop-filter は要素の後ろに見えているものを加工します。写真の上に半透明のカードを置き、後ろだけをぼかすと、すりガラスのように見えます。
すりガラスのカード
.filters {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
}
figure { margin: 0; }
img { display: block; inline-size: 100%; }
figcaption { font-size: 11px; text-align: center; }
/* filter:要素そのものを加工する */
.gray { filter: grayscale(1); } /* 白黒にする */
.blur { filter: blur(3px); } /* ぼかす */
.dark { filter: brightness(.6) saturate(1.5); } /* 暗く、色を濃く。空白で区切って重ねがけ */
/* backdrop-filter:要素の後ろに見えているものを加工する */
.glass-stage {
display: grid;
place-items: center;
block-size: 140px;
margin-block-start: 12px;
border-radius: 12px;
/* 後ろの模様。ぼかし具合が分かりやすいよう、縞にしている */
background-image: repeating-linear-gradient(45deg, #2f80b8 0 14px, #ff6a3d 14px 28px);
}
.glass {
margin: 0;
padding: 14px 22px;
border: 1px solid rgb(255 255 255 / 60%);
border-radius: 12px;
background-color: rgb(255 255 255 / 35%); /* 半透明にしないと後ろが見えない */
backdrop-filter: blur(8px);
font-weight: 700;
}
backdrop-filter は、背景色を半透明にしたときだけ効果が見えます。ぼかしはブラウザの負担が大きいので、画面いっぱいの広い面や、たくさんの要素に使うのは控えめに。文字が読みにくくなっていないかも確かめます。
10学習レベル:基礎
文字と単位
本文の読みやすさは、文字サイズ・行間・1 行の長さでほぼ決まります。凝った装飾より先にここを整えます。
読みやすい本文の作り方
行の長さは全角 30〜40 文字くらいに収めると、目が次の行を見つけやすくなります。行間は 1.7 前後。文字を大きくするより、まず余白と行間を整えるほうが効きます。
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 | CSS の基準の長さ。高精細な画面では、画面の点 2〜3 個ぶんで描かれる | 線幅・角丸など、文字サイズの設定に連動させたくないもの |
% | 親要素に対する割合 | 幅 |
vw / dvh | 画面幅・画面高さの 1% | 全画面の見出しなど。dvh はスマホのアドレスバー対応 |
ch | 文字「0」の幅 | 1 行の文字数を決めたいとき |
fr | 余った空間の分け前 | Grid 専用 |
clamp(最小, 可変, 最大) と書くと、画面幅に応じてなめらかに変わり、小さくなりすぎも大きくなりすぎもしません。メディアクエリで文字サイズを何段階も刻む必要がなくなります。
11学習レベル:基礎
CSS Grid — ページの骨組みを組む
Web サイトの header・nav・main・aside・footer を、行と列の両方で配置します。名前付きの領域を使うと、ページの形を CSS から読み取れます。
本文
ページの中心となる内容です。
/* 狭い画面を基本にする。JS が使えなくても nav は表示する。 */
.site-demo {
display: grid;
position: relative;
gap: 10px;
grid-template-areas:
"header"
"nav"
"main"
"aside"
"footer";
}
.site-demo > header { grid-area: header; background: #dcecf7; }
.site-demo > nav { grid-area: nav; background: #eaf2f8; }
.site-demo > main { grid-area: main; background: #fff3e9; }
.site-demo > aside { grid-area: aside; background: #e9f4ed; }
.site-demo > footer { grid-area: footer; background: #dcecf7; }
.site-demo > * {
padding: 14px;
border-radius: 8px;
}
.site-demo h3 { margin: 0 0 6px; }
.site-demo p { margin: 0; }
.site-demo header { display: grid; grid-template-columns: 1fr auto; align-items: center; }
.site-demo .menu-toggle { display: none; }
/* リンクを 110px 以上で、入る数だけ並べる */
.site-demo nav {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
gap: 8px;
}
.site-demo nav a {
padding: 8px;
border-radius: 6px;
background: #2f80b8;
color: #fff;
text-align: center;
}
/* JS が動くと、狭い画面では nav を開閉式にする */
.site-demo.is-enhanced {
grid-template-areas:
"header"
"main"
"aside"
"footer";
}
.site-demo.is-enhanced .menu-toggle { display: inline-block; }
.site-demo.is-enhanced > nav {
display: none;
position: absolute;
inset-block-start: 62px;
inset-inline: 0;
z-index: 1;
box-shadow: 0 8px 20px #0003;
}
.site-demo.is-enhanced.menu-open > nav { display: grid; }
@media (width >= 768px) {
/* 外側の Grid だけ組み替え、本文と関連情報を横に並べる。 */
.site-demo, .site-demo.is-enhanced {
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
grid-template-areas:
"header header"
"nav nav"
"main aside"
"footer footer";
}
.site-demo.is-enhanced .menu-toggle { display: none; }
.site-demo.is-enhanced > nav {
display: grid;
position: static;
box-shadow: none;
}
}
幅が広いと本文と関連情報が 2 列、狭いと 1 列になります。狭い画面では「☰ メニュー」でナビゲーションを開けます。
プレビューの骨組みは次の HTML です。nav のリンクは通常のページ移動に使い、メニューボタンは開閉だけを担当します。
<div class="site-demo">
<header>
<strong>サイト名</strong>
<button class="menu-toggle" type="button"
aria-controls="sample-nav" aria-expanded="false">☰ メニュー</button>
</header>
<nav id="sample-nav" aria-label="サイトのメニュー">
<a href="/">ホーム</a>
<a href="/services">サービス</a>
<a href="/news">お知らせ</a>
<a href="/contact">お問い合わせ</a>
</nav>
<main>本文</main>
<aside>関連情報</aside>
<footer>© サイト名</footer>
</div>
<script src="./menu.js" defer></script>const page = document.querySelector(".site-demo");
const menuButton = page.querySelector(".menu-toggle");
// JS が使えるときだけ、スマホの nav を開閉式にする。
page.classList.add("is-enhanced");
menuButton.addEventListener("click", () => {
const isOpen = page.classList.toggle("menu-open");
menuButton.setAttribute("aria-expanded", String(isOpen));
});
page.addEventListener("keydown", (event) => {
if (event.key !== "Escape") return;
page.classList.remove("menu-open");
menuButton.setAttribute("aria-expanded", "false");
});2 種類の Grid
外側の Grid は grid-template-areas でページの領域を決めます。広い画面では main と aside を横に並べます。狭い画面では JavaScript が使える場合だけ nav を開閉式にして、Grid を header / main / aside / footer の 4 段にします。使えない場合は nav を通常の段として表示します。minmax(0, 2fr) minmax(0, 1fr) は、間隔を除いた幅を 2 対 1 に分けます。768px はこの例の切り替え幅で、実際のサイトでは本文とサイドの内容に合わせて調整します。
内側の nav も Grid です。repeat(auto-fit, minmax(110px, 1fr)) は、各リンクに最低 110px を確保しながら入る列数を自動で決め、残りの幅を分けます。この自動反復なら、リンクが増減しても列数を手で書き直す必要がありません。auto-fill は空の列も残す点が異なります。
gap は行・列のすき間です。1fr は gap を除いた余りを分けます。列幅を自分でパーセント計算するより、意図が明確になります。
Grid で領域の見た目を変えても、キーボード操作や読み上げの順序は HTML の並びに従います。この例は header → nav → main → aside → footer の順を保っています。ボタンの aria-expanded も開閉に合わせて更新します。
1 つの .grid で並べる じゃんけんの 2 列の配置 グリッドの基本 grid-template-areas grid-template-columns repeat() minmax() gap
12学習レベル:実践
Flexbox — 一列の中身を整える
ページの骨組みは Grid で作れます。Flexbox は、中身の幅に合わせて一方向へ並べ、余白を配置するときに便利です。例では、記事の状態表示と公開ボタン、タグの一覧表示に Flexbox を使っています。
.editor-bar {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px 16px;
padding: 12px;
background: #eaf2f8;
border-radius: 8px;
}
.tool-action {
margin-inline-start: auto; /* 残りの空間を受けて右端へ */
}
.tag-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-block-start: 12px;
}
.tag-row span {
padding: 6px 10px;
background: #2f80b8;
color: #fff;
border-radius: 999px;
}
margin-inline-start: auto は同じ行の残りの空間を受け取り、操作ボタンを端に寄せます。flex-wrap: wrap でタグが次の行へ移ると、各行が中身に合わせて独立に並びます。Grid のように上下の列位置を揃える必要がない場面に合います。
gap は Grid と Flexbox の両方で使えます。要素間だけにすき間を作り、外側には余白を足しません。gap: 8px 16px は「行の間 8px、列の間 16px」です。
行と列の配置を決めるなら Grid、中身の大きさを生かして一方向の余白を配るなら Flexbox を試します。
タグと店主のご挨拶 じゃんけんの残りの高さを埋める履歴 フレックスボックスの基本 グリッドと他のレイアウト方法 flex-wrap align-items
13学習レベル:基礎
レスポンシブ — 画面幅と、要素の幅
スマホから大画面まで、1 つの CSS で対応します。基準は「狭い画面の指定を先に書き、広くなったら足す」(モバイルファースト)。こうすると打ち消しの指定が減ります。
まずは先に学んだ Grid を画面幅で組み替える方法を確かめましょう。後半のコンテナクエリは、同じ部品を幅の違う場所で再利用したくなったときに読めば十分です。
ブレークポイントの出発点
次の数値は、よく見かける例です。端末の種類を厳密に分ける境界でも、すべてのサイトに必要な設定でもありません。画面を少しずつ広げ、内容が窮屈になったり間延びしたりする場所で切り替えます。
| 幅の例 | 切り替えを考える場面 |
|---|---|
480px | 小さな部品の並びや余白を変える |
768px | 本文とサイドバーを 2 列にする |
1024px | カード一覧の列数を増やす |
1280px | 広い画面で余白や表示密度を調整する |
必要になった幅だけを採用します。たとえば サイトの Grid 例は 768px の 1 か所だけです。文字サイズの設定も考慮したい場合は、ブレークポイントを em で書く方法もあります。
/* まず狭い画面の指定を書く */
.cards { display: grid; gap: 12px; }
/* 必要な幅だけ追加する。この例では 768px と 1024px */
@media (width >= 768px) {
.cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (width >= 1024px) {
.cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* 範囲もそのまま書ける */
@media (480px <= width < 768px) { }
/* 従来の書き方も引き続き使える: @media (min-width: 768px) */
/* 画面幅以外にも聞ける */
@media (prefers-reduced-motion: reduce) { } /* 動きを減らす設定 */
@media (prefers-color-scheme: dark) { } /* ダークモード */
@media (pointer: coarse) { } /* 指で操作している */
@media print { } /* 印刷時 */
コンテナクエリ — 「置かれた場所の幅」で変える
同じカードでも、サイドバーに置けば狭く、メインに置けば広い。画面幅ではなく親の幅で切り替えられるのがコンテナクエリです。部品を「どこに置いても正しく見える」ようにできます。
↓ 枠の右下をドラッグして幅を変えてみてください
幅で形が変わるカード
広いときだけ、タグと本文が横並びになります。
.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 枚かぶせています。
メディアクエリなしで列を変える じゃんけんの成績パネルとコンテナクエリ @media @container container-type
14学習レベル:実践
配置と重なり
通常の要素は上から順に積まれます(これを「通常フロー」と呼びます)。そこから外して自由に置きたいときだけ position を使います。
スクロールしてもくっつく見出し
この枠の中をスクロールしてみてください。
見出しが上端に貼り付いたまま残ります。
position: sticky は、通常フローのまま「ここまで来たら止まる」を指定できます。
ページ全体のヘッダー固定にもよく使われます。
下までスクロールすると、親要素の終わりで一緒に流れていきます。
.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
- フローから外れ、
positionがstatic以外の、いちばん近い祖先を基準に置かれる(ふつうはrelativeを付けた親)。 - fixed
- 画面に固定。スクロールしても動かない。
- sticky
- ふだんは通常フロー、指定位置まで来ると貼り付く。上端に貼り付けるなら
inset-block-startを指定します。 - z-index
- 重なりの順番。位置指定した要素のほか、Grid や Flexbox の子要素にも効きます。
15学習レベル:発展
:is() :where() :has()
セレクタを短く書ける :is()、詳細度をゼロにする :where()、そして「中身によって親の見た目を変える」ことができる :has()。主要ブラウザの新しい版で使えます。古い環境も対象にする場合は、対応状況を確認しましょう。
/* :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; } /* 未入力なら送信を薄く */
おすすめ商品
NEWバッジ付き。
通常商品
バッジなし。
.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 行も書かずに、親要素の見た目が変わります。
フォームの入力エラーを示す
HTML の入力条件に合わない欄は :invalid で選べます。必須欄は最初から空欄なので、操作後にエラーを示したいときは :user-invalid が適しています。色だけに頼らず、ブラウザのエラーメッセージや説明文も残します。
.contact-form :is(input, textarea):user-invalid {
border: 2px solid #b42318;
}
.contact-form :is(input, textarea):focus-visible {
outline: 3px solid #2f80b8;
outline-offset: 2px;
}:user-invalid は入力や送信を試みた後の状態に合わせた表示に使います。JavaScript で独自のエラーを示す例も参照してください。
選んだ行を :has() で強調 じゃんけんの :has() で止めるスクロール :is() :where() :has() :user-invalid
16学習レベル:発展
@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; } /* こちらが勝つ */
}
層の順番が詳細度より優先されます。
だから「コンポーネントの指定が、あとから足したユーティリティに勝ってしまう」といった事故が起きません。
なお、どの層にも入れずに書いた指定は、すべての層より強くなります。@layer を使い始めたら、指定はどれかの層に入れておくと、順番が読みやすくなります。
ネスト記法
セレクタの繰り返しを書かずに済む書き方です。Sass のような別ツールを入れなくても、素の CSS で使えます。
子孫セレクタを書くだけなら & は要りません。
&(自分自身を指す記号)が必要なのは、次の 3 つの場合だけです。
- 擬似クラス・擬似要素を付ける —
&:hover&::before - 自分自身にクラスや属性を足す —
&.is-open&[disabled] - 親を後ろに置きたい —
.sidebar &(=.sidebar .card)
.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 段を超えると、どの要素の話か分からなくなります。
17学習レベル:実践
ダークモード
色をすべてカスタムプロパティにまとめておけば、ダークモード対応は「変数を差し替えるだけ」で済みます。個別の場所で色を直接書いていると、ここで手間がかかります。
: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);
}
「OS 設定に従う(既定)」「手動でライト」「手動でダーク」の 3 通りがあります。上のように :not([data-theme="light"]) を挟むと、
手動の選択が OS 設定に必ず勝つようになります。このページの右上のボタンも、まさにこの仕組みで動いています。
18学習レベル:実践
動きをつける
状態が変わるときになめらかにするのが transition、
放っておいても動き続けるのが animation です。動かすのは opacity と transform 系にとどめると、なめらかに動きます。
読み込み中
.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) {
.button { transition: none; }
.dot { animation: none; } /* 点滅し続ける動き(infinite)は止める */
}
スクロールの演出 じゃんけんの手を揺らすアニメーション transition animation @keyframes prefers-reduced-motion
ずらす・回す・拡大する — translate、rotate、scale
上の例の translate には、rotate(回す)と scale(拡大・縮小)という仲間がいます。どれも見た目だけを動かし、周りの要素の位置は変えません。3 つは別々のプロパティなので、:hover で 1 つだけ変えても、ほかの 2 つはそのまま残ります。
.tiles {
display: flex;
gap: 16px;
padding: 16px;
}
.tile {
display: grid;
place-items: center;
inline-size: 90px;
block-size: 90px;
border-radius: 12px;
background-color: #2f80b8;
color: #fff;
font-size: 12px;
transition: translate .3s ease, rotate .3s ease, scale .3s ease;
}
.move:hover { translate: 0 -10px; } /* 横 縦 の順。マイナスで上へ */
.turn:hover { rotate: 15deg; } /* deg は角度。プラスで時計回り */
.grow:hover { scale: 1.15; } /* 1 が元の大きさ */
@media (prefers-reduced-motion: reduce) {
.tile { transition: none; }
}
それぞれにマウスを乗せてみてください。以前は transform: translate(0, -10px) rotate(15deg); のように 1 行にまとめて書いていました。この書き方では、1 つだけ変えたいときも全部を書き直す必要があります。
傾けて重ねたカード translate rotate scale
状態はクラスや属性で表す
「送信できない」「残りが少ない」などは、JavaScript で色を直接何度も書き換えるより、状態を示すクラスや属性を切り替えて CSS に見た目を任せます。進捗バーの幅のように値で変わる部分にはカスタムプロパティを渡せます。
.progress-track {
background-color: #eaf2f8;
border-radius: 999px;
overflow: hidden;
}
.progress-fill {
block-size: .8rem;
inline-size: var(--progress, 0%);
background-color: #3aa76d;
transition: inline-size .3s ease;
}
.progress-panel[data-state="warning"] .progress-fill { background-color: #d94f24; }
.button:disabled { opacity: .5; cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) {
.progress-fill { transition: none; }
}
JavaScript 側は panel.dataset.state = "warning" や bar.style.setProperty("--progress", "25%") として状態と値だけを渡します。
19
早見表:知っておくと助かるもの
| プロパティ | できること |
|---|---|
gap | Flex / Grid の間隔。margin で空けるより簡単 |
min() max() clamp() | 計算して値を決める。min(100% - 2rem, 1100px) が定番 |
overflow-wrap: anywhere | 長い URL がはみ出すのを防ぐ |
text-wrap: balance / pretty | 見出しと本文の折り返しを整える |
scroll-margin-block-start | 固定ヘッダーの下にアンカーが隠れるのを防ぐ(HTML 編「ページ内リンク」) |
accent-color | チェックボックスやスライダーの色をブランド色に |
:focus-visible | キーボード操作のときだけ枠を出す。消さない |
place-items: center | Grid の中央寄せがこれ 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 が担当します。
HTML 編で作ったページを Grid で並べ、狭い画面では縦一列にしてみましょう。ブラウザの幅を少しずつ変え、文字や画像が窮屈になる場所を探して切り替えます。
実行結果がその場に出るので、書きながら確かめられます。