divで済ませない👿:セマンティックHTMLの基本
なぜ今更セマンティックHTMLを書くのかと思うかもしれませんが自分の整理のためなのでお許し下さい🙏
なぜセマンティックHTMLが重要なのか
HTMLには100以上の要素が存在します。(多すぎんだろ…)
しかし、多くのWebサイトを見てみると <div> と <span> が大量に使われていることに気づく人も多くいるのではないでしょうか?
これは要素の持つ意味(セマンティクス)を活かしきれていない状態と言えるかもしれません。
セマンティックHTMLが重要な理由は主に3つあると自分は考えています。
- アクセシビリティ: スクリーンリーダーは要素の種類を読み上げる。
<nav>なら「ナビゲーション」、<button>なら「ボタン」とユーザーに伝えてくれる - SEO: 検索エンジンは構造を理解してページを評価する。
<article>や<h1>の使い方によって文書構造が適切に伝わる - 保守性: コードを読む人間にとっても、
<div class="header">より<header>の方が意図が明確になる
「見た目が同じなら別に<div>でいいのでは」と思うかもしれません。確かに見た目だけなら同じになると思います。
しかしHTMLは見た目を定義するものではなく文書の構造と意味を定義するものです。
見た目はCSSの役割だと思うのでここをしっかりと使い分けていきましょう。
この役割分担を意識することでより良いWebページを作ることができると思ってます。
構造を示す要素(ランドマーク)
ページの骨格を作る要素について見ていきましょう。これらは「ランドマーク」とも呼ばれ、スクリーンリーダーユーザーがページ内をジャンプする際の目印となります。
よくある書き方
<div class="header">
<div class="logo">サイト名</div>
<div class="nav">
<div class="nav-item">ホーム</div>
<div class="nav-item">記事</div>
</div>
</div>
<div class="main">
<div class="article">
<div class="article-title">記事タイトル</div>
<div class="article-content">本文...</div>
</div>
<div class="sidebar">関連記事</div>
</div>
<div class="footer">© 2026</div>
すべて <div> で構成されており、クラス名を読まないと何が何だかわからない状態になっていますね。
今は中々見ない(PJによるか?)かもしれませんが昔はよく見かけたコードでした。
セマンティックな書き方
<header>
<h1>サイト名</h1>
<nav>
<ul>
<li><a href="/">ホーム</a></li>
<li><a href="/posts">記事</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h2>記事タイトル</h2>
<p>本文...</p>
</article>
<aside>関連記事</aside>
</main>
<footer>© 2026</footer>
各要素の役割が一目でわかるようになったのではないでしょうか。
主要なランドマーク要素
| 要素 | 役割 | 対応するARIAロール |
|---|---|---|
<header> | ページやセクションのヘッダー | banner(※1) |
<nav> | ナビゲーションリンク群 | navigation |
<main> | ページのメインコンテンツ(1ページに1つ) | main |
<article> | 独立した記事コンテンツ | article |
<section> | テーマを持つセクション | region(※2) |
<aside> | メインコンテンツと間接的に関連する内容 | complementary |
<footer> | ページやセクションのフッター | contentinfo(※1) |
- ※1:
<header>と<footer>がbanner/contentinfoロールを持つのは、セクショニングコンテンツ(article, aside, main, nav, section)の中にネストされていない場合のみ。例えば<article>内の<header>はbannerロールを持たない - ※2:
<section>がregionロールを持つのは、アクセシブルネーム(aria-label,aria-labelledby,titleなど)を持つ場合のみ。単に見出しがあるだけではregionにはならない
なお、この表はあくまで参考情報です。セマンティックなHTML要素を使えば、対応するロールは暗黙的に付与されるため、明示的に role 属性を追加する必要はほぼないです。
むしろ、不用意に role を指定すると暗黙のロールを上書きしてしまうリスクがあるので注意が必要ですね。
<section> と <div> の使い分けで迷う方は多いのではないでしょうか。
基本的な考え方として、意味的なまとまりを持つセクションであれば <section> を、単なるスタイリング目的のグルーピングであれば <div> を使うとよいと思います。<section> を使う場合は、見出しを含めてそのセクションが何であるかを明確にすることが推奨されています。
見出し階層
見出し要素 <h1> 〜 <h6> は文書のアウトラインを形成する重要な要素です。
陥りやすい間違い
<!-- 見た目で選んでしまう -->
<h3>大きすぎるから h3 にしよう</h3>
<!-- 階層をスキップしてしまう -->
<h1>タイトル</h1>
<h4>いきなり h4</h4>
見出しレベルは見た目ではなく構造で決めるべきだと思います。 見た目を変えたい場合はCSSで調整しましょう。(大事)
正しい階層
<h1>ページタイトル</h1>
<h2>セクション1</h2>
<h3>サブセクション1-1</h3>
<h3>サブセクション1-2</h3>
<h2>セクション2</h2>
<h3>サブセクション2-1</h3>
見出し(h1〜h6)は文書の構造(階層)を表します。基本は h1 → h2 → h3 のように、セクションの入れ子に合わせて段階的にネストしていきます。
h2 の次にいきなり h4 が来るなど、見出しレベルを飛ばすと、スクリーンリーダー利用者が見出し一覧でページを把握する際に「この間に h3 相当の内容がある(はず)?」と構造が読み取りづらくなるため、可能な限り避けるべきです。W3C/WAIのチュートリアルでも「見出しレベルのスキップは混乱を招くことがあるので、避けられるなら避ける」とされています。
一方で、順番抜かしは「絶対禁止」というより、意味のある見出し構造を壊さないことが最優先です。W3C/WAIも、例えばサブセクションが終わって上位セクションに戻るようなケース(h4 の後に新しい h2 が来る等)は問題にならない、と説明しています。
なお、見出し要素は見た目のサイズ調整のために使うものではありません。小さく見せたい場合は見出しレベルを下げるのではなくCSSで調整しましょう。
h1 は1ページに1つにすべきか
よく議論されるテーマかなと思います。
HTML Standard上、h1 を複数使っても文法上ただちに誤りになるわけではありません。
ただし実務では、次の理由から 1ページに1つ(=そのページの主題を表す h1 を1つ)に寄せておく方が安全だと考えています。
- 支援技術やツールが、複数の
h1を常に“意図通りの階層”として扱えるとは限らず、読み手にとって主題が曖昧になることがある(特にアウトライン関連の扱いは頼りづらい) - SEO観点でも「複数 h1 が直ちに不利」とは限らない一方、1つにしておくと“主題”が伝わりやすく、監査ツールの指摘も減らせる(Googleも“複数H1はクリティカルではない”というスタンス)
結論として、複数 h1 が絶対NGというより、読み手にとって主題が明確で、見出し構造が一貫していることが最優先で、迷うなら h1 は1つにしておくのが安牌かなと自分は考えてます。
よく間違えやすい要素
<button> と <div onClick>
この間違いは非常に多く見られると思います。
// div に onClick を付けるパターン
<div className="btn" onClick={handleClick}>
送信
</div>
// button 要素を使うパターン
<button type="button" onClick={handleClick}>
送信
</button>
<div onClick> には以下の問題があります。
- キーボードでフォーカスできない(
tabindex="0"を追加すれば可能) - EnterやSpaceキーで押せない(
onKeyDownを追加すれば可能) - スクリーンリーダーが「ボタン」と認識しない(
role="button"を追加すれば可能)
お気づきだろうか、これらはすべて <button> を使えば最初から備わっている機能です。
<div>でボタンを再現しようとすると多くの属性やイベントハンドラを追加する必要があり、しかも漏れが生じやすいです。素直に<button>を使う方が安全で効率的だと思います。
<a> と <button> の使い分け
どちらもクリックできる要素ですが、役割が異なります。
| 要素 | 役割 | 使用例 |
|---|---|---|
<a> | 別のページや場所に移動する | ページ遷移、アンカーリンク |
<button> | アクションを実行する | フォーム送信、モーダルを開く、カウントを増やす |
<!-- ページ移動には a を使う -->
<a href="/about">会社概要</a>
<!-- アクション実行には button を使う -->
<button type="button" onclick="openModal()">詳細を見る</button>
<!-- よくある間違い: ボタンの見た目にしたいから a を使う -->
<a href="#" onclick="doSomething(); return false;">実行</a>
最後の例は <a href="#"> でアクションを実行するパターンですが、これはリンクの本来の意味を損なってしまっています。ボタンの見た目が欲しければ、<button> にCSSを適用すればよいです。
リスト要素の使い分け
<!-- ul: 順序のないリスト -->
<ul>
<li>りんご</li>
<li>みかん</li>
<li>バナナ</li>
</ul>
<!-- ol: 順序のあるリスト -->
<ol>
<li>材料を準備する</li>
<li>鍋に水を入れる</li>
<li>沸騰したらパスタを入れる</li>
</ol>
<!-- dl: 用語と説明のペア -->
<dl>
<dt>HTML</dt>
<dd>文書の構造を定義するマークアップ言語</dd>
<dt>CSS</dt>
<dd>文書の見た目を定義するスタイルシート言語</dd>
</dl>
ナビゲーションのリンク一覧も <ul> + <li> で書くのが適切です。
<nav>
<ul>
<li><a href="/">ホーム</a></li>
<li><a href="/about">会社概要</a></li>
<li><a href="/contact">お問い合わせ</a></li>
</ul>
</nav>
<table> の正しい使い方
レイアウト目的で <table> を使うのは過去の手法であり、現在は避けるべきとされています。
アクセシビリティ(支援技術への配慮)と保守性の観点から非推奨・禁止されており、本来のデータ表現(表形式データ)にのみ使用しましょう。
<table>
<caption>2026年1月の売上</caption>
<thead>
<tr>
<th scope="col">商品名</th>
<th scope="col">数量</th>
<th scope="col">金額</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">商品A</th>
<td>100</td>
<td>¥10,000</td>
</tr>
<tr>
<th scope="row">商品B</th>
<td>50</td>
<td>¥7,500</td>
</tr>
</tbody>
</table>
ポイントを整理すると以下のようになります。
<caption>で表のタイトルを示す<thead>,<tbody>でヘッダーと本体を分ける<th>のscope属性でヘッダーセルの方向を示す(colは列、rowは行)
これらの要素を適切に使うことで、スクリーンリーダーがセルとヘッダーの関係を正しく伝えられるようになります。
フォーム要素
フォームはアクセシビリティにおいて特に重要な領域です。 ユーザーが情報を入力する場面では、適切なマークアップがないと操作に支障をきたすことがあります。
label と入力欄の関連付け
<!-- label がない -->
<input type="text" placeholder="名前" />
<!-- label はあるが関連付けがない -->
<label>名前</label>
<input type="text" />
<!-- for 属性で関連付け -->
<label for="name">名前</label>
<input type="text" id="name" />
<!-- label で input を囲む(暗黙的な関連付け) -->
<label>
名前
<input type="text" />
</label>
<label>が正しく関連付けられていると、ラベルをクリックした際に入力欄にフォーカスが移動する。これはマウス操作でもタッチ操作でも便利な機能。
なお、placeholderは<label>の代わりにはならないです。s
入力を始めると消えてしまうため「何を入力すべきか」という情報が失われてしまいます。
fieldset と legend
関連するフォーム項目をグループ化するために使用します。
<fieldset>
<legend>配送先住所</legend>
<label for="zip">郵便番号</label>
<input type="text" id="zip" />
<label for="address">住所</label>
<input type="text" id="address" />
</fieldset>
<fieldset>
<legend>お支払い方法</legend>
<label>
<input type="radio" name="payment" value="card" />
クレジットカード
</label>
<label>
<input type="radio" name="payment" value="bank" />
銀行振込
</label>
</fieldset>
特にラジオボタンやチェックボックスのグループでは、<fieldset> + <legend> の使用を強く推奨されています。
スクリーンリーダーは「お支払い方法、ラジオボタングループ」のように読み上げてくれるため、ユーザーは今何を選択しようとしているのかを理解しやすくなります。
適切な input type
<!-- ブラウザが適切なキーボードや UI を提供してくれる -->
<input type="email" /> <!-- メールアドレス用キーボード -->
<input type="tel" /> <!-- 電話番号用キーボード -->
<input type="url" /> <!-- URL用キーボード -->
<input type="number" /> <!-- 数字入力 + スピナー -->
<input type="date" /> <!-- 日付ピッカー -->
<input type="search" /> <!-- 検索用(クリアボタンなど) -->
type="text" ですべて済ませてしまうのはもったいない。適切なtypeを指定することで、モバイルでは最適なキーボードが表示され、ブラウザによってはバリデーションや専用のUIも提供される。
検証方法
書いたHTMLがセマンティックかどうか、ツールで確認することをお勧めします。
ブラウザの開発者ツール
Chrome DevToolsやFirefox DevToolsには「アクセシビリティ」タブが用意されています。 要素を選択するとその要素がどのようなロールを持ち、どのような名前でスクリーンリーダーに伝わるかを確認できます。
axe DevTools
ブラウザ拡張機能として提供されており、ページをスキャンしてアクセシビリティの問題を検出してくれます。 「ラベルのない入力欄」「コントラスト不足」「見出し階層のスキップ」などを自動で発見できるため、セルフチェックに便利です。
見出し構造の確認
Chrome DevToolsのコンソールで以下のコードを実行すると、ページの見出し構造を確認できます。
document.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(h => {
console.log(' '.repeat(parseInt(h.tagName[1]) - 1) + h.tagName + ': ' + h.textContent.trim());
});
アウトラインが綺麗な階層構造になっていれば、見出しの使い方は適切だと判断できると思います。
まとめ
紹介した内容を整理すると以下のようになります。
- div と span は汎用要素であり、適切なセマンティック要素がないか検討した上で使う
- ランドマーク要素(header, nav, main, article, aside, footer)でページの骨格を構築する
- 見出し階層は見た目ではなく文書構造に基づいて決める。h1 → h2 → h3 と順番に使う
- インタラクティブな要素には適切な要素を選ぶ。ボタンは
<button>、リンクは<a> - フォームでは label を必ず関連付ける。placeholder はラベルの代替にはならない
- ツールで検証する習慣をつける。axe DevTools や開発者ツールのアクセシビリティタブを活用する
セマンティックHTMLは地味な分野ではあるがこれらを意識できているかどうかでコードの品質は大きく変わってくる。ぜひ日々の開発で意識してみてください。