HTML と CSS
この部の 5 / 9 章 ・ 全体で 38 / 76 章 ・ 読了目安 50 分
- 意味のあるタグで構造を書ける
- フォームの送信内容と HTTP を対応づけられる
- DevTools で「CSS が効かない」を自分で解決できる
「自分はバックエンド担当だから HTML は要らない」——これは通用しません。
- ブラウザの DevTools を開くと、そこにあるのは HTML と CSS です
- API が返した値が画面に出ない時、原因の切り分けにはどちらの知識も要ります
- 管理画面や社内ツールは、たいてい自分で作ることになります
- そしてフォームの仕組みは、Web と HTTPの HTTP と直結しています
この章は、Web ページを構成する2つの言語を扱います。 プログラミング言語ではありません。HTML は構造を、CSS は見た目を書くものです。
HTML は文書の構造を書く
<article class="post">
<h1>タイトル</h1>
<p>本文です。<a href="/next">次へ</a></p>
</article>- タグで囲んで、それが何であるかを示す
- 属性(
classhref)で情報を足す - 入れ子にして構造を作る
ブラウザはこれを読んで DOM ツリーを作ります(ブラウザはなぜ動くのか)。
意味のあるタグを使う
すべてを div で書くこともできます。しかし、しません。
<!-- 悪い: 何であるか分からない -->
<div class="header">
<div class="nav">...</div>
</div>
<div class="content">
<div class="title">記事タイトル</div>
</div>
<!-- 良い: タグ自体が意味を持つ -->
<header>
<nav>...</nav>
</header>
<main>
<h1>記事タイトル</h1>
</main>理由は3つあります。
- スクリーンリーダーが「見出し一覧」「ナビゲーション」として扱える
- 検索エンジンが構造を理解できる
- 他の人がコードを読める
| タグ | 意味 |
|---|---|
header / footer | ページやセクションの上下 |
nav | ナビゲーション |
main | ページの主要部分(1ページに1つ) |
article | それ単体で完結する内容 |
section | 意味のまとまり |
h1〜h6 | 見出しの階層 |
ul / ol / li | リスト |
table | 表形式のデータ(レイアウト目的では使わない) |
button | 押すもの |
a | 移動するもの |
<div onclick="save()">保存</div> <!-- 悪い -->
<button onclick="save()">保存</button> <!-- 良い -->div に onclick を付けると、見た目は同じでも次が失われます。
- キーボードで到達できない(Tab で選べない)
- Enter / Space で押せない
- スクリーンリーダーが「ボタン」と読み上げない
使い分けは単純です。移動するなら a、動作するなら button。
h1 の次に h4 を使う、というのは見た目を理由にやりがちです。
見出しは文書の目次であって、文字サイズの指定ではありません。 大きさを変えたいなら CSS で変えてください。
フォーム — HTTP と直結している部分
Web と HTTPで学んだ HTTP のメソッドとボディは、ここから生まれます。
<form action="/login" method="post">
<label for="email">メールアドレス</label>
<input type="email" id="email" name="email" required>
<label for="password">パスワード</label>
<input type="password" id="password" name="password" required>
<button type="submit">ログイン</button>
</form>送信ボタンを押すと、ブラウザは自動的にこの HTTP リクエストを作ります。
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
email=a%40example.com&password=secret| 属性 | 役割 |
|---|---|
action | 送り先の URL |
method | get(クエリ文字列になる)か post(ボディになる) |
name | 送信されるキー。これが無い入力欄は送信されない |
type | 入力の種類(email number date checkbox ...) |
required | 未入力なら送信をブロックする |
「入力したのにサーバーに届かない」の原因の第1位です。
id は CSS と label 用、name が送信用です。両方必要です。
required や type="email"、maxlength は、
ユーザーの入力ミスをその場で教えるための機能です。
DevTools で属性を消せば送信できますし、curl なら最初から関係ありません。
サーバー側の検証は必ず別に必要です(セキュリティ)。
「フロントで弾いているから大丈夫」は、通用しません。
label を必ず関連付ける
<label for="email">メールアドレス</label>
<input id="email" name="email">for と id を一致させると、
- ラベルをクリックすると入力欄にフォーカスが移る(スマホで特に効く)
- スクリーンリーダーが「メールアドレス、編集可能」と読む
チェックボックスでは、押しやすさが目に見えて変わります。
アクセシビリティは特別なことではない
「対応が必要になったらやる」ものではなく、 正しい HTML を書けば、ほとんど自動的に満たされます。
□ 見出し(h1〜h6)で構造を作る
□ 押すものは button、移動は a
□ 画像に alt を書く(装飾なら alt="" と空で書く)
□ フォームに label を付ける
□ キーボードだけで全部の操作ができる(Tab と Enter で試す)
□ 色だけで情報を伝えない(赤字だけでエラーを示さない)
□ 文字と背景のコントラストを確保する
role="button" や aria-label は、
正しいタグで表現できない時にだけ使います。
button を使えば role="button" は要りません。
「ARIA を足す」より「正しいタグに直す」が先です。
CSS — 見た目を書く
セレクタ {
プロパティ: 値;
}
.post h1 {
font-size: 1.5rem;
color: #333;
}セレクタと詳細度
同じ要素に複数のルールが当たった時、どれが勝つかが決まっています。
| セレクタ | 例 | 強さ |
|---|---|---|
| 要素 | p | 弱い |
| クラス | .title | 中 |
| ID | #main | 強い |
| インラインスタイル | style="..." | もっと強い |
!important | 最強(使わない) |
「なぜかスタイルが当たらない」時に !important を足すと、その場は解決します。
しかし次に上書きしたい人は、さらに !important を足すしかなくなります。
数ヶ月後には、誰も何が効いているか分からない CSS が残ります。
まず DevTools でなぜ負けているかを見てください。 打ち消し線の付いたプロパティが「負けたルール」です。
ボックスモデル
すべての要素は、内側から順に内容・padding・border・marginを持つ箱です。
┌─────────── margin ───────────┐
│ ┌───────── border ─────────┐ │
│ │ ┌─────── padding ──────┐ │ │
│ │ │ 内容(content) │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
/* 実務ではほぼ必ず最初に書く */
*, *::before, *::after { box-sizing: border-box; }これを書くと、width: 300px が padding と border を含んだ幅になります。
書かないと、padding を足すたびに要素が大きくなり、レイアウトが崩れます。
上下に隣接する margin は、足し算ではなく大きいほうだけが適用されます。
上の要素の margin-bottom: 20px
下の要素の margin-top: 30px
→ 間隔は 50px ではなく 30px
「隙間が思ったより狭い」の原因の多くはこれです。
padding に変えるか、Flexbox の gap を使うと起きません。
レイアウトは Flexbox と Grid
現代の CSS では、この2つでほぼすべてのレイアウトが組めます。
float や table レイアウトは過去のものです。
/* Flexbox: 一方向に並べる */
.toolbar {
display: flex;
justify-content: space-between; /* 主軸方向(横)の配置 */
align-items: center; /* 交差軸方向(縦)の配置 */
gap: 8px; /* 要素間の余白。margin より扱いやすい */
}
/* Grid: 二次元に配置する */
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 16px;
}上の Grid の1行は、「240px を下回らない範囲で、入るだけ列を作る」という意味です。 これだけでメディアクエリ無しにレスポンシブになります。
| 使い分け | |
|---|---|
| Flex | 1方向に並べる(ツールバー、リスト、ボタンの列) |
| Grid | 2次元に配置する(カード一覧、ページ全体の骨格) |
位置指定
position: static; /* 既定。通常の流れに従う */
position: relative; /* 通常の位置から相対的にずらす。基準点にもなる */
position: absolute; /* 直近の relative な祖先を基準に配置。流れから外れる */
position: fixed; /* 画面に固定 */
position: sticky; /* スクロールに応じて固定される */z-index: 9999 を書いても前に出ない、ということが起きます。
原因はスタッキングコンテキストです。
親要素が position と z-index、opacity、transform などを持つと、
その中で新しい重なりの階層が作られ、子はその中でしか戦えません。
対策は「数字を大きくする」ではなく、
要素を DOM 上のどこに置くかを見直すことです。
モーダルをページの最上位(body 直下)に描画するのは、これが理由です。
単位
| 単位 | 基準 | 使いどころ |
|---|---|---|
px | 絶対 | border など、変わってほしくないもの |
rem | ルートの文字サイズ | 文字・余白の基本 |
em | その要素の文字サイズ | 文字に連動させたい余白 |
% | 親の大きさ | 幅 |
vw / vh | 画面の幅・高さ | 全画面表示 |
文字サイズを px で固定すると、ブラウザの文字サイズ設定が効かなくなります。
基本は rem を使ってください。
レスポンシブ
/* 既定はモバイル向けに書き、広い画面で上書きする(モバイルファースト) */
.container { padding: 16px; }
@media (min-width: 768px) {
.container { padding: 32px; max-width: 960px; margin-inline: auto; }
}:root { --bg: #fff; --text: #222; }
@media (prefers-color-scheme: dark) {
:root { --bg: #111; --text: #eee; }
}
body { background: var(--bg); color: var(--text); }色を CSS 変数にまとめておくと、テーマの切り替えが1箇所で済みます。 この教科書のサイトも同じ作りです。
CSS が破綻する理由と、その対策
CSS のセレクタはグローバルです。
.title と書けば、ページ中のすべての .title に当たります。
規模が大きくなると、次が起きます。
- どこで使われているか分からず、消せない CSS が積み上がる
- 別の画面のスタイルを壊す
- 詳細度の戦争(
!importantの応酬)
対策として、いくつかの流儀があります。
| 方式 | 考え方 |
|---|---|
| BEM | block__element--modifier という命名規則で衝突を避ける |
| CSS Modules | ビルド時にクラス名を一意な文字列に変換する |
| CSS-in-JS | コンポーネントの中にスタイルを書く |
| Tailwind | 既成の細かいクラスを組み合わせ、CSS を新規に書かない |
どれが正しいということはなく、チームで統一されていることが重要です。 この教科書のサイトは Tailwind を使っています。
DevTools での見方
Web と HTTPの Network タブと並んで、Elements タブは最も使う道具です。
1. 要素を右クリック →「検証」で、その要素の HTML にジャンプする
2. 右側の Styles で、当たっている CSS が上から強い順に並ぶ
3. 打ち消し線 = そのルールは負けている(詳細度か、後勝ちで)
4. Computed タブ = 最終的に何が適用されたか
5. その場で値を書き換えて試せる(リロードで消える)
「CSS が効かない」の8割は、DevTools を見れば5秒で分かります。 効いていないのか、当たっているが上書きされているのか、 そもそも要素が違うのかが、その場で判別できます。
セキュリティの XSS そのものです。
el.innerHTML = userInput; // 危険。<script> やイベント属性が実行される
el.textContent = userInput; // 安全。文字として表示される表示するだけなら textContent。 これは例外なく守ってください。
ボタンとして機能する要素を作ります。最も適切なのはどれですか。
実務の落とし穴まとめ
- すべて
div— 読めず、キーボードでも操作できない inputにnameが無い — サーバーに届かない- HTML のバリデーションを防御と考える — 迂回できる。サーバー側で必ず検証
labelを付けない — 押しにくく、読み上げられない!importantで解決 — 次に上書きしたい人が同じことをするbox-sizingを設定していない — padding のたびにレイアウトが崩れる- margin の相殺を知らない — 隙間が想定と合わない
z-indexを大きくして解決しようとする — スタッキングコンテキストを見る- 文字サイズを
pxで固定 — ユーザーの設定が効かない innerHTMLにユーザー入力 — XSS
まとめ
- HTML は構造、CSS は見た目。意味のあるタグを選ぶことが、 そのままアクセシビリティと可読性になる
- 押すなら
button、移動ならa - フォームは HTTP と直結している。
nameが送信キー、 HTML のバリデーションは防御ではない - CSS は詳細度で勝敗が決まる。
!importantではなく DevTools で原因を見る - レイアウトは Flexbox(1方向)と Grid(2次元)。
gapを使う box-sizing: border-boxは最初に書く。remを基本の単位にする- CSS はグローバルなので、命名規則かツールで衝突を防ぐ
- DevTools の Elements タブが最短の解決手段
公式ドキュメント
迷ったら一次情報に戻ってください。
| 対象 | リンク |
|---|---|
| MDN HTML(日本語) | https://developer.mozilla.org/ja/docs/Web/HTML |
| MDN CSS(日本語) | https://developer.mozilla.org/ja/docs/Web/CSS |
| MDN アクセシビリティ | https://developer.mozilla.org/ja/docs/Web/Accessibility |
| WAI-ARIA オーサリングプラクティス | https://www.w3.org/WAI/ARIA/apg/ |
章末問題
フォームに入力した値が、サーバー側で空になっています。最初に確認すべきは?
モーダルを表示したが、ページ上部のヘッダーの下に隠れてしまいます。z-index を 99999 にしても変わりません。原因として最も可能性が高いのは?
次の章では、書いた HTML と CSS で何を作るべきか—— デザインと使いやすさの話に進みます。