Old-School Front-End / 2026 ・ No.02
いにしえのフロントエンド入門
第2回 HTML の骨組み ── 申請一覧を「意味のあるタグ」で組み立てる
公開
AI に頼まず、一行ずつ手で書く「いにしえの」フロントエンド入門。第2回から、いよいよ工事が始まります。
今回作るのは、経費精算システムの申請一覧の骨組み。CSS も JavaScript も使わず、HTML だけで組み立てます。
キーワードは「意味のあるタグ」。見た目が同じでも、タグの選び方ひとつで、読み上げソフトにも、テストの道具にも、AI にも、
ページの中身の伝わり方がまるで変わります。最後は、書いた HTML を検査ツールにかけるところまで進みます。
今回のゴール
新井先輩、申請一覧の画面、AI に作ってもらいました! ほら、ちゃんと表示されます。
古井どれどれ……div、div、div、div。見事な「div スープ」だな。
新井でも、見た目はちゃんとしてますよ?
古井見た目はな。今日は、見た目の下にある「骨」の話だ。
いまここ:第2回
今回は、前回「建設予定地」だった index.html を、申請一覧のページに建て替えます。中身は次のとおりです。
- ページの頭 ── ロゴとサイト名、メニュー(申請一覧・承認・ダッシュボード)、ログイン中の利用者
- ページの本体 ── 大見出し、新規申請へのリンク、今月のまとめ、申請の一覧表
- ページの足 ── 著作権の表示
CSS はまだ使わないので、見た目は素朴なままです(服を着せるのは第4回)。そのぶん、どのタグを選ぶかに集中できます。
タグ・要素・属性
まずは部品の名前から。HTML は、タグで文字を挟んで「これは何か」を示す言語です。
タグの解剖図
- 要素 ── 開始タグ・中身・終了タグをまとめたもの。「a 要素」「table 要素」のように呼ぶ
- 属性 ── 要素に付ける追加の情報。開始タグの中に 名前="値" の形で書く
- 空要素 ── 中身を持たない要素。終了タグも書かない(img、br、input、meta など)
どの要素にも付けられる属性のうち、よく使うのはこの三つです。
| 属性 | 意味 | 例 |
|---|---|---|
| id | ページの中で一つだけの名前。同じ id を2回使ってはいけない | <table id="list"> |
| class | グループの名前。何個に付けてもよい。CSS(第4回)や JavaScript(第8回)で狙い撃ちするときに使う | <td class="amount"> |
| lang | 中身の言語。ふつうは html 要素に一度だけ | <html lang="ja"> |
もう一つ大事なのが、入れ子のきまりです。要素の中に要素を入れるときは、箱の中に箱を入れるように、後から開いたタグを先に閉じます。
入れ子のきまり
前回、</h1> を消したら全部が見出しになったのを覚えているでしょうか。ブラウザは間違いがあってもエラーを出さず、自分なりに木を組み直して表示します。その「自分なり」が、こちらの意図と合っている保証はありません。
ページの区画(ランドマーク)
ページはふつう、頭(ヘッダー)・メニュー・本体・足(フッター)に分かれます。HTML には、それぞれ専用のタグがあります。こうした区画をランドマーク(目印)と呼びます。
div スープと、意味のあるタグ
| タグ | 意味 | この連載での使いどころ |
|---|---|---|
| header | ページ(または区画)の頭 | ロゴ・サイト名・利用者名 |
| nav | 主なナビゲーション(メニュー) | 申請一覧・承認・ダッシュボードへのリンク |
| main | そのページの主役。1ページに1つ | 見出し・まとめ・一覧表 |
| section | 見出しを持つ、ひとまとまりの内容 | 「今月のまとめ」「申請の一覧」 |
| article | それだけで独立して意味を持つ内容 | (ブログの記事、ニュースの1件など) |
| aside | 本筋の脇にある補足 | (第13回のダッシュボードで使うかも) |
| footer | ページ(または区画)の足 | 著作権表示・問い合わせ先 |
| div | 意味のない箱。見た目のためだけにまとめたいとき | ほかに合うタグがないときの最後の手段 |
では、div だけで作ったページと、意味のあるタグで作ったページを比べてみましょう。
画面で見る限り、ほとんど同じです。ところが、読み上げソフトなどの支援技術には、まるで違って見えています。ブラウザがページから作る「アクセシビリティの木」を、テストの道具 Playwright で取り出してみました(実際の出力。長い部分は省略)。
# ① div スープ:ほぼ「画像1枚と、ひと続きの文字」
- img
- text: 経費精算システム • 申請一覧 • 承認 • ダッシュボード 新井 太郎(申請者) 申請一覧 + 新 …
# ② 意味のあるタグ:区画・見出し・リスト・表がそのまま伝わる
- banner:
- paragraph:
- link "経費精算システム":
- /url: index.html
- navigation:
- list:
- listitem:
- link "申請一覧":
- /url: index.html
- listitem:
- link "承認":
- /url: approve.html
- listitem:
- link "ダッシュボード":
- /url: dashboard.html
- paragraph: 新井 太郎(申請者)
- main:
- heading "申請一覧" [level=1]
- paragraph:
- link "+ 新規申請":
- /url: form.html
- heading "今月のまとめ" [level=2]
- list:
- listitem: 今月の申請:4 件
- listitem:
- text: 承認待ちの金額:
- strong: 32,400 円
- listitem: 差し戻し:1 件
- heading "申請の一覧" [level=2]
- table "2026年10月の申請(新しい順)":
…
②では、banner(header)、navigation(nav)、main、見出しのレベル、リストの数、表の見出しまでが伝わっています。①では、リンクの役割すら消えてしまいました(span をクリックで動くようにしただけなので、キーボードの Tab キーでもたどり着けません)。
- 読み上げソフトの利用者 ── 見出しを飛び移ってページの全体をつかむ人が多い(WebAIM の調査)。ランドマークも同じように「目次」として使える。div スープでは、頭から全部聞くしかない
- テストの道具 ── 開発ガイドのPlaywright 入門と実践で紹介した getByRole('navigation') のような探し方は、意味のあるタグがあって初めて使える
- AI のツール ── ブラウザを操作する AI も、こうした役割の情報を手がかりに画面を読む。人にとって分かりやすいページは、機械にとっても分かりやすい
古井div は「名無しの箱」だ。中に何が入っているか、外からは誰にも分からん。
新井AI に頼むときも「意味のあるタグで」って一言足せばいいんですね。……で、出てきたのが正しいかは、自分で読んで確かめる、と。
古井分かってきたじゃないか。
DOM の木
第1回で、ブラウザは HTML を読んで DOM という木を作る、と書きました。今回のページを木にすると、こうなります。
申請一覧のページの DOM(主な部分)
インデント(字下げ)をそろえて HTML を書くと、コードの形がそのまま木の形になります。字下げは見た目の飾りではなく、木の形を目で確かめるための道具です。ブラウザで要素を右クリックして「検証」を選ぶと、開発者ツールの Elements タブで、この木を実物で見られます。
あとで CSS は「木のどの枝に服を着せるか」、JavaScript は「木のどの枝を動かすか」を指定します。きれいな木を作っておくことが、後半を楽にする一番の近道です。
見出し・段落・リスト
見出しには h1 から h6 までの6段があります。大事なのは、大きさではなく、文章の構造で選ぶこと。
見出しは目次になる
かつて HTML の仕様には、section の入れ子から見出しの段を自動で決める仕組み(アウトライン・アルゴリズム)が書かれていましたが、どのブラウザも実装しないまま、2022年に仕様から削除されました。見出しの段は、自分で正しく付けるのが今のきまりです。
| タグ | 意味 | 申請一覧では |
|---|---|---|
| h1〜h6 | 見出し(段の数字が小さいほど大きなまとまり) | h1「申請一覧」、h2「今月のまとめ」「申請の一覧」 |
| p | 段落 | サイト名、利用者名、新規申請のリンク |
| ul / ol / li | 順番のないリスト/順番のあるリスト/その1項目 | メニュー、今月のまとめ |
| strong | 重要(強く読む) | 承認待ちの金額 |
| em | 強調(言い方を変える) | (今回は使わない) |
| small | 注記・著作権などの小さな文字 | フッターの著作権表示 |
| time | 日付や時刻。datetime 属性に機械が読める形で書く | 申請日 |
メニューを ul で書くのは、「項目が3つある、ひとまとまりのリスト」だからです。読み上げソフトは「リスト、3項目」と先に教えてくれます。<br> で改行を重ねて並べると、この情報は伝わりません。
表を組む
申請の一覧は、行(1件の申請)と列(申請日・件名・金額・状態)のあるデータです。こういうデータには table を使います。
表の部品
<table>
<caption>2026年10月の申請(新しい順)</caption>
<thead>
<tr>
<th scope="col">No.</th>
<th scope="col">申請日</th>
<!-- …件名・金額(円)・状態… -->
</tr>
</thead>
<tbody>
<tr>
<td>0007</td>
<td><time datetime="2026-10-16">2026-10-16</time></td>
<!-- …1行に、見出しと同じ数のマス… -->
</tr>
</tbody>
</table>
古井昔はな、画面の配置を全部 table で組んだもんだ。隙間を作るのに、透明な小さい画像を敷き詰めてな。
新井それはさすがに、いにしえすぎます。
古井だから言うんだ。表は表のためだけに使え。配置は CSS の仕事だ(第5回)。
- 状態は文字で書く ── あとで「申請中は黄色、差し戻しは赤」と色を付けても、色だけに頼らず文字を残す。色の見分けにくい人や、白黒で印刷したときにも伝わる
- 金額の列 ── 「金額(円)」と見出しに単位を書けば、各マスは数字だけで済む。右寄せにするのは CSS の仕事(第4回)
- 行の数は決め打ちしない ── 今は手で4行書いているが、第10回からはデータを読み込んで JavaScript で行を作る
リンクと画像
リンクは a 要素の href、画像は img 要素の src で、行き先(ファイルの場所)を書きます。書き方は三種類あります。
リンク先の書き方
| 書き方 | 例 | 使いどころ |
|---|---|---|
| 相対パス | form.html、img/logo.svg、../index.html | 自分のサイトの中。今いるファイルからの道順 |
| 絶対 URL | https://getbootstrap.com/ | ほかのサイト |
| ページ内リンク | #list | 同じページの id="list" の場所へ |
リンクを新しいタブで開く target="_blank" には、昔は安全のために rel="noopener" を必ず添えましたが、今の主要なブラウザはこれを自動で付けてくれます。ただ、業務システムで勝手にタブが増えるのは嫌がられがち。新しいタブは、外部のサイトや PDF を開くときくらいにしておきましょう。
画像で忘れてはいけないのが alt(代わりの文字)です。画像が見えない人や、画像が読み込めなかったときに、代わりに伝わる文章です。
| 画像の種類 | alt の書き方 | 例 |
|---|---|---|
| 意味を伝える画像 | 画像が伝えている内容を書く | alt="領収書:タクシー代 2,400円" |
| 飾りの画像 | 空にする(alt="" と書く。省略はしない) | 区切りの模様など |
| 横に同じ内容の文字がある画像 | 空にする(同じことを2回読ませない) | 今回のロゴ。すぐ横に「経費精算システム」と書いてある |
width と height も書いておきましょう。画像が読み込まれる前から場所が確保されるので、読み込み途中にページがガタッとずれるのを防げます。
申請一覧を組み立てる
ここまでの部品を組み合わせた、今回の完成形です。前回の index.html を、まるごと書き換えます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>申請一覧 | 経費精算システム</title>
</head>
<body>
<header>
<p><a href="/"><img src="img/logo.svg" alt="" width="32" height="32"> 経費精算システム</a></p>
<nav>
<ul>
<li><a href="/">申請一覧</a></li>
<li><a href="approve.html">承認</a></li>
<li><a href="dashboard.html">ダッシュボード</a></li>
</ul>
</nav>
<p>新井 太郎(申請者)</p>
</header>
<main>
<h1>申請一覧</h1>
<p><a href="form.html">+ 新規申請</a></p>
<section>
<h2>今月のまとめ</h2>
<ul>
<li>今月の申請:4 件</li>
<li>承認待ちの金額:<strong>32,400 円</strong></li>
<li>差し戻し:1 件</li>
</ul>
</section>
<section>
<h2>申請の一覧</h2>
<table>
<caption>2026年10月の申請(新しい順)</caption>
<thead>
<tr>
<th scope="col">No.</th>
<th scope="col">申請日</th>
<th scope="col">件名</th>
<th scope="col">金額(円)</th>
<th scope="col">状態</th>
</tr>
</thead>
<tbody>
<tr>
<td>0007</td>
<td><time datetime="2026-10-16">2026-10-16</time></td>
<td><a href="detail.html">大阪出張(交通費・宿泊費)</a></td>
<td>32,400</td>
<td>申請中</td>
</tr>
<tr>
<td>0006</td>
<td><time datetime="2026-10-15">2026-10-15</time></td>
<td><a href="detail.html">客先打ち合わせの電車代</a></td>
<td>3,830</td>
<td>承認済み</td>
</tr>
<tr>
<td>0005</td>
<td><time datetime="2026-10-09">2026-10-09</time></td>
<td><a href="detail.html">技術書の購入</a></td>
<td>4,180</td>
<td>差し戻し</td>
</tr>
<tr>
<td>0004</td>
<td><time datetime="2026-10-02">2026-10-02</time></td>
<td><a href="detail.html">勉強会の参加費</a></td>
<td>3,000</td>
<td>精算済み</td>
</tr>
</tbody>
</table>
</section>
</main>
<footer>
<p><small>© 2026 KIJI 経費精算システム(練習用)</small></p>
</footer>
</body>
</html>
- head の中 ── 前回の charset と title に、meta name="viewport" を足した。スマホで画面の幅に合わせて表示するためのおまじない(詳しくは第5回)。title は「ページ名 | サイト名」の順にすると、タブがたくさん開いていても見分けやすい
- 同じ「経費精算システム」でも ── header の中ではサイト名なので p、main の中のページ名「申請一覧」が h1。どちらを h1 にするかで迷ったら、「このページの題名はどれか」で決める
- 件名のリンク先 ── 今は全部 detail.html。どの申請の詳細を出すかは、第10〜12回で URL に番号を付けて渡す
メニューの「承認」「ダッシュボード」や、リンク先の form.html、detail.html は、まだ存在しません。押すと「ファイルが見つからない」と表示されますが、それで正常です。これから順に建てていきます。
書いたら検査する
ブラウザは間違いを黙って飲み込んでしまうので、HTML の文法は、専用の検査ツールで確かめます。W3C が公開している Nu Html Checker(validator.w3.org/nu)なら、ファイルを渡すか、コードを貼り付けるだけで使えます。わざと3つ間違えた HTML を検査してみました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>申請一覧</title>
</head>
<body>
<header>
<p><img src="img/logo.svg"> 経費精算システム</p>
</header>
<main>
<h1 id="title">申請一覧</h1>
<p><strong><a href="form.html">+ 新規申請</strong></a></p>
<h2 id="title">申請の一覧</h2>
</main>
</body>
</html>
結果はこうなりました(実際の出力。長い行は折り返し)。
Error: An “img” element must have an “alt” attribute, except under
certain conditions. For details, consult guidance on
providing text alternatives for images. (9行目)
Error: End tag “strong” violates nesting rules. (13行目)
Error: No “a” element in scope but a “a” end tag seen. (13行目)
Error: Duplicate ID “title”. (14行目)
Warning: The first occurrence of ID “title” was here. (12行目)
Document checking completed.
| 指摘 | 意味 | 直し方 |
|---|---|---|
| img に alt がない | 画像の代わりの文字がない | ロゴは横にサイト名があるので alt="" |
| strong の終了タグが入れ子のきまりに違反 | a より先に strong を閉じている(箱の交差) | </a></strong> の順に閉じる |
| id「title」が重複 | 一つだけのはずの id が2か所に | id を変えるか、要らなければ消す |
8章の完成形を同じツールにかけると、「No errors found(エラーなし)」と出ます。
AI が書いた HTML も、まず検査に通すのがおすすめです。AI は文法の間違いこそ少ないものの、div スープや、見出しの段の飛ばし、alt の書き忘れはよくあります。検査ツールが見つけるのは文法の間違いまでで、「このタグで意味が合っているか」は、人が読んで確かめるしかありません。
結 — 一枚まとめと次回予告
- HTML は、タグで中身を挟んで「これは何か」を示す言語。開始タグ・中身・終了タグで一つの要素。属性は 名前="値"。入れ子は、後から開いたものを先に閉じる。
- header・nav・main・footer などのランドマークでページを区切る。div は「名無しの箱」で、最後の手段。
- 見た目が同じでも、意味のあるタグは、読み上げソフト・テストの道具・AI に、ページの構造をそのまま伝える。
- 見出しは段を飛ばさず構造で選ぶ。表は行と列のデータのためだけに使い、caption・thead・th scope を付ける。画像には alt を。
- 書いたら Nu Html Checker で検査。AI が書いた HTML も同じ。意味が合っているかは、人が読んで確かめる。
新井骨組みはできました! 次は「新規申請」を押した先ですね。
古井そう、第3回は申請フォームだ。入力欄・選択肢・ファイルの添付。業務システムの本丸だぞ。
参考
- WHATWG — HTML Living Standard
- W3C — W3C and WHATWG to work together to advance the open Web platform (2019)
- MDN — HTML elements reference
- MDN — <table>: The Table element
- MDN — <img>: The Image Embed element
- Can I use — a: target="_blank" implies rel="noopener"
- Bruce Lawson — Why the HTML Outlining Algorithm was removed from the spec (2022)
- WebAIM — Screen Reader User Survey
- W3C — Nu Html Checker
- Playwright — Snapshot testing (ARIA snapshots)
ページの写真は、筆者が作ったデモページを2026年10月17日にクラウド上の Chromium(Playwright)で撮影したものです。アクセシビリティの木は Playwright の ariaSnapshot() の実際の出力(長い部分を省略)、検査の結果は Nu Html Checker(vnu-jar 26.10.12)を手元で実行した実際の出力です。target="_blank" の扱いは Can I use の互換表、アウトライン・アルゴリズムの削除は Bruce Lawson 氏の解説(2022年)によります。登場人物は架空の人物です。
いにしえのフロントエンド入門 第2回 — HTML の骨組み:申請一覧を「意味のあるタグ」で組み立てる / 2026.10