プログラマのための IT 教科書

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>
  • タグで囲んで、それが何であるかを示す
  • 属性(class href)で情報を足す
  • 入れ子にして構造を作る

ブラウザはこれを読んで 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つあります。

  1. スクリーンリーダーが「見出し一覧」「ナビゲーション」として扱える
  2. 検索エンジンが構造を理解できる
  3. 他の人がコードを読める
タグ意味
header / footerページやセクションの上下
navナビゲーション
mainページの主要部分(1ページに1つ)
articleそれ単体で完結する内容
section意味のまとまり
h1〜h6見出しの階層
ul / ol / liリスト
table表形式のデータ(レイアウト目的では使わない)
button押すもの
a移動するもの
`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
methodget(クエリ文字列になる)か post(ボディになる)
name送信されるキー。これが無い入力欄は送信されない
type入力の種類(email number date checkbox ...)
required未入力なら送信をブロックする
`name` の付け忘れ

「入力したのにサーバーに届かない」の原因の第1位です。 id は CSS と label 用、name が送信用です。両方必要です。

HTML のバリデーションは UX のためであって、防御ではない

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 で試す)
□ 色だけで情報を伝えない(赤字だけでエラーを示さない)
□ 文字と背景のコントラストを確保する
ARIA は最後の手段

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 を足すと、その場は解決します。

しかし次に上書きしたい人は、さらに !important を足すしかなくなります。 数ヶ月後には、誰も何が効いているか分からない CSS が残ります。

まず DevTools でなぜ負けているかを見てください。 打ち消し線の付いたプロパティが「負けたルール」です。

ボックスモデル

すべての要素は、内側から順に内容・padding・border・marginを持つ箱です。

┌─────────── margin ───────────┐
│ ┌───────── border ─────────┐ │
│ │ ┌─────── padding ──────┐ │ │
│ │ │      内容(content)   │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
/* 実務ではほぼ必ず最初に書く */
*, *::before, *::after { box-sizing: border-box; }

これを書くと、width: 300px が padding と border を含んだ幅になります。 書かないと、padding を足すたびに要素が大きくなり、レイアウトが崩れます。

margin の相殺

上下に隣接する 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 を下回らない範囲で、入るだけ列を作る」という意味です。 これだけでメディアクエリ無しにレスポンシブになります。

使い分け
Flex1方向に並べる(ツールバー、リスト、ボタンの列)
Grid2次元に配置する(カード一覧、ページ全体の骨格)

位置指定

position: static;    /* 既定。通常の流れに従う */
position: relative;  /* 通常の位置から相対的にずらす。基準点にもなる */
position: absolute;  /* 直近の relative な祖先を基準に配置。流れから外れる */
position: fixed;     /* 画面に固定 */
position: sticky;    /* スクロールに応じて固定される */
z-index の泥沼

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 の応酬)

対策として、いくつかの流儀があります。

方式考え方
BEMblock__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秒で分かります。 効いていないのか、当たっているが上書きされているのか、 そもそも要素が違うのかが、その場で判別できます。

`innerHTML` に外部の文字列を入れない

セキュリティの XSS そのものです。

el.innerHTML = userInput;   // 危険。<script> やイベント属性が実行される
el.textContent = userInput; // 安全。文字として表示される

表示するだけなら textContent。 これは例外なく守ってください。

ボタンとして機能する要素を作ります。最も適切なのはどれですか。

実務の落とし穴まとめ

  1. すべて div — 読めず、キーボードでも操作できない
  2. input に name が無い — サーバーに届かない
  3. HTML のバリデーションを防御と考える — 迂回できる。サーバー側で必ず検証
  4. label を付けない — 押しにくく、読み上げられない
  5. !important で解決 — 次に上書きしたい人が同じことをする
  6. box-sizing を設定していない — padding のたびにレイアウトが崩れる
  7. margin の相殺を知らない — 隙間が想定と合わない
  8. z-index を大きくして解決しようとする — スタッキングコンテキストを見る
  9. 文字サイズを px で固定 — ユーザーの設定が効かない
  10. 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 で何を作るべきか—— デザインと使いやすさの話に進みます。

読み終わったら記録しておくと、目次で進み具合が分かります。