Skip to content

Day 2: セマンティック HTML — ページの構造を伝えるタグ

今日のゴール

  • HTML タグが見た目だけでなく「ページの構造」を機械に伝えていることを知る
  • ランドマーク、見出し階層、セクショニングの3つの構造を知る
  • React のコンポーネント分割で見出しレベルが壊れやすい問題を知る

人間と機械はページの見方が違う

Web ページを開いたとき、人間は見た目で瞬時に構造を把握できます。「上にメニューがあるな」「ここがメインの内容だな」「右にサイドバーがあるな」と、レイアウトや文字の大きさから判断しています。

しかし、検索エンジンのクローラーやスクリーンリーダー(画面読み上げソフト)は見た目を認識できません。代わりに、HTML のタグから構造を読み取っています

つまり、HTML のタグ選びは見た目の問題ではなく、ページの構造を正しく伝えられるかどうかの問題です。

HTML がページの構造を伝える仕組みは大きく3つあります。

1. ランドマーク — ページの「地図」

<header><nav><main><footer> などのタグは、ページの大まかな領域を示します。これらはランドマーク(目印)と呼ばれ、ページのどこに何があるかを示す「地図」の役割を果たします。

html
<header>
  <p><a href="/">My Blog</a></p>
  <nav>
    <ul>
      <li><a href="/">ホーム</a></li>
      <li><a href="/posts">記事一覧</a></li>
    </ul>
  </nav>
</header>
<main>
  <h1>今日のニュース</h1>
  <p>新機能をリリースしました。</p>
</main>
<footer>
  <p>&copy; 2026 My Blog</p>
</footer>

カッコ内の bannernavigation などは、ブラウザがタグから自動的に割り当てるロール(役割)です。スクリーンリーダーはこのロールを使って「ナビゲーションに移動」「メインコンテンツに移動」といった操作を提供します。

主要なランドマークタグは以下の通りです。

タグロール意味
<header>bannerページやセクションの導入部分
<nav>navigation主要なナビゲーション
<main>mainページの主要コンテンツ(ページに1つ)
<aside>complementary主要コンテンツの補足情報
<footer>contentinfoページやセクションの末尾情報

これらのタグをすべて <div> に置き換えても、見た目は CSS で同じにできます。しかしその場合、機械から見るとページの「地図」が存在しないことになります。

自分のページにどんなランドマークがあるかは、ブラウザ拡張の Landmark Navigation via Keyboard or Pop-upChrome / Firefox)で確認できます。ランドマークの一覧がポップアップで表示され、ページのどこに何があるかが一目でわかります。

2. 見出し階層 — ページの「目次」

<h1> から <h6> まで、6段階の見出しタグがあります。これらはページの目次(アウトライン)を構成します。

html
<h1>料理レシピ</h1>
  <h2>和食</h2>
    <h3>肉じゃが</h3>
    <h3>味噌汁</h3>
  <h2>洋食</h2>
    <h3>ハンバーグ</h3>

この見出し構造は、本の目次と同じ「章 → 節 → 項」の階層です。

料理レシピ
├── 和食
│   ├── 肉じゃが
│   └── 味噌汁
└── 洋食
    └── ハンバーグ

スクリーンリーダーのユーザーは、見出し一覧を表示してページ内を移動できます。検索エンジンも見出しの階層からページの内容を把握しています。

見出しレベルは飛ばさない

見出しレベルは h1 → h2 → h3 のように順番に深くします。h1 の次にいきなり h3 を使うと、目次の階層が壊れます。

html
<!-- ❌ h2 を飛ばしている -->
<h1>お知らせ</h1>
<h3>2026年4月のお知らせ</h3>

<!-- ✅ 正しい階層 -->
<h1>お知らせ</h1>
<h2>2026年4月のお知らせ</h2>

深い階層から浅い階層に戻るのは問題ありません。上の例でも、h3(肉じゃが)の後に h2(洋食)に戻っています。本の目次でも節が終われば次の章に移るのは自然です。

なお、<h1> はページの最上位の見出しなので、ページに1つが基本です。h1 が複数あるときは、階層が正しく作れていないサインです。

文字サイズを変えたいだけなら CSS

ブラウザは h1 を大きく、h2 をやや小さく表示します。この見た目のせいで「文字を大きくしたいから h1」「小さくしたいから h4」という使い方をしてしまうことがあります。

文字の大きさを変えたいだけなら CSS の font-size を使います。タグは「意味」を、CSS は「見た目」を担当する ── HTML と CSS の基本的な役割分担です。

デザイン上、見出しを表示できないときは

逆に、デザイン上「見出しを表示したくない」場面もあります。たとえば検索フォームのセクションで、視覚的にはアイコンだけで十分だが、スクリーンリーダー利用者には「検索」という見出しがあった方が構造が伝わる、というケースです。

こんなときは、見出しを視覚的には隠しつつ、スクリーンリーダーからは読めるようにするテクニックを使います。sr-only という慣例のクラス名でよく知られています(Tailwind CSS にも同名のユーティリティがあります)。

html
<section>
  <h2 class="sr-only">検索</h2>
  <form>
    <input type="search" aria-label="検索語句" />
    <button type="submit">🔍</button>
  </form>
</section>
css
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}

見た目の都合で見出しを省略するのではなく、「視覚的に隠す」選択肢があることを知っておくと、見出し階層を崩さずにデザイン要求に応えられます。

3. セクショニング — コンテンツのまとまり

<section><article> は、コンテンツのまとまりを示すタグです。

html
<main>
  <h1>技術ブログ</h1>

  <article>
    <h2>React 入門</h2>
    <p>React はUIライブラリです。</p>
  </article>

  <article>
    <h2>Next.js の概要</h2>
    <p>Next.js は React のフレームワークです。</p>
  </article>
</main>

使い分けの基準はシンプルです。

  • <article>: それだけ取り出しても意味が通じる、独立したコンテンツ。ブログの1記事、1つのコメント、1つの商品カードなど
  • <section>: テーマでまとまったひとかたまり。「料金プラン」「よくある質問」など、見出しを伴うのが自然

<article> は「別の場所に貼っても成立するか?」で判断するとわかりやすいです。ブログ記事は RSS フィードに載せても成立しますが、「料金プラン」セクションだけ取り出しても意味が通じません。

セクショニングと見出し

<section> にはそのまとまりのタイトルとして見出しを付けるのが自然です。さらに、小見出し(h3 など)がある場合は <section> でラップすると、どこからどこまでがそのサブセクションなのかが明確になります。

html
<main>
  <h1>料理レシピ</h1>

  <section>
    <h2>和食</h2>
    <p>和食の代表的なレシピを紹介します。</p>

    <section>
      <h3>肉じゃが</h3>
      <p>材料:じゃがいも、牛肉、にんじん...</p>
    </section>

    <section>
      <h3>味噌汁</h3>
      <p>材料:豆腐、わかめ、味噌...</p>
    </section>
  </section>

  <section>
    <h2>洋食</h2>
    <h3>ハンバーグ</h3>
    <p>材料:ひき肉、玉ねぎ、パン粉...</p>
  </section>
</main>

<section> でまとまりを囲み、見出しでタイトルを付ける。この組み合わせがページの構造を作ります。

React のコンポーネント分割で見出しレベルが壊れやすい

React ではページを小さなコンポーネントに分割します。ここで見出しレベルの問題が起きやすくなります。

tsx
// 常に h2 を出力するコンポーネント
function SectionTitle({ children }: { children: React.ReactNode }) {
  return <h2>{children}</h2>;
}

function Page() {
  return (
    <main>
      <h1>料理レシピ</h1>
      {/* ここでは h2 が正しい */}
      <SectionTitle>和食</SectionTitle>
      <section>
        {/* ここでは h3 が欲しいが、h2 が出力されてしまう */}
        <SectionTitle>肉じゃが</SectionTitle>
        <p>材料:じゃがいも、牛肉、にんじん...</p>
      </section>
    </main>
  );
}

出力される HTML は h1 → h2 → h2 となり、「肉じゃが」が「和食」の下位ではなく同じレベルに見えてしまいます。

対策として、見出しレベルを props で受け取る設計があります。

tsx
function SectionTitle({
  level,
  children,
}: {
  level: 1 | 2 | 3 | 4 | 5 | 6;
  children: React.ReactNode;
}) {
  const Tag = `h${level}` as const;
  return <Tag>{children}</Tag>;
}

function Page() {
  return (
    <main>
      <h1>料理レシピ</h1>
      <SectionTitle level={2}>和食</SectionTitle>
      <section>
        <SectionTitle level={3}>肉じゃが</SectionTitle>
        <p>材料:じゃがいも、牛肉、にんじん...</p>
      </section>
    </main>
  );
}

コンポーネントを作るときは「ページのどの階層で使われるか」を意識することが大切です。

div を使ってよい場面

ここまで「タグで意味を伝える」話をしてきましたが、<div> にも正しい使いどころがあります。

レイアウトやスタイリングのためにグループ化が必要で、意味的なタグが当てはまらないときに使います。

html
<main>
  <article>
    <h1>記事タイトル</h1>
    <!-- 2カラムレイアウトのためのラッパー。意味的なタグが当てはまらない -->
    <div style="display: flex; gap: 16px;">
      <div>
        <p>本文がここに入ります。</p>
      </div>
      <div>
        <img src="photo.jpg" alt="記事に関連する写真" width="300" height="200" />
      </div>
    </div>
  </article>
</main>

ポイントは 「まず意味のあるタグを検討し、当てはまらないときに div を使う」 という順番です。

まとめ

  • HTML タグは見た目ではなく、ページの構造を機械に伝えるもの
  • ランドマーク(header, nav, main, aside, footer)はページの「地図」を作る
  • 見出し階層(h1〜h6)はページの「目次」を作る。レベルを飛ばさない、h1 はページに1つ
  • セクショニング(section, article)はコンテンツのまとまりを示す。article は独立して成立するもの
  • React のコンポーネント分割では見出しレベルが壊れやすい。props でレベルを指定する設計が有効
  • div はレイアウト用。意味のあるタグが当てはまらないときに使う