HTMLテーブルの作り方|見出し・結合・読みやすい表構造を学ぶ
※本記事にはPRを含む場合があります。
こんにちは。『脱会社員』脱サラを応援し続けるブログ、運営者の『なかびー』です。
HTMLで表を作るときは、tableで表全体、trで行、thで見出し、tdでデータを表します。見た目を罫線で囲む前に、どの値がどの見出しに対応するかを決めることが大切です。captionで表の題名を付け、scopeで行見出しと列見出しを区別すると、表の意味を伝えやすくなります。
この記事では架空の工房の営業時間表と料金比較表を作ります。HTMLの構造、セル結合、最小限のCSS、狭い画面での横スクロール、保存後の検品までを一巡できるコードを掲載します。Webページ全体のレイアウトを表で組む使い方ではなく、行と列の関係があるデータを扱います。
仕様は2026年10月4日に確認したMDNとW3C WAIの資料に基づきます。HTML Living Standardの表要素と現行CSSを想定し、掲載コードはPythonとlxmlによる構造・内容照合を実施しています。検証環境の制約によりブラウザーでの実表示、スマートフォン実機、スクリーンリーダーの読み上げは未検証です。表示テストの方法と期待値は示しますが、実行済みとは区別します。掲載画像は説明用の生成イメージです。
表にする情報を選び、行・列・単位をそろえる

同じ項目を比べるデータにtableを使う
表に向くのは、同じ項目を複数の対象について比べたい情報です。曜日ごとの営業時間なら、曜日、開店時刻、閉店時刻という列が作れます。コースごとの料金なら、コース名、時間、料金という列に分けられます。一つの値を見たときに、行の対象と列の項目を組み合わせて意味が分かることが基本です。
単に画像を左、文章を右へ置きたい場合や、ページ全体を三列にしたい場合は、通常CSSのレイアウト機能を検討します。見た目をそろえるためだけに空のセルを大量に作ると、HTMLの意味と見た目がずれます。MDNのHTMLの表の基本でも、表は表形式データのために使い、レイアウト用途と分ける考え方が説明されています。
文章を表へ押し込めば読みやすくなるわけではありません。各セルが何百文字もの説明になるなら、見出し付きの文章へ分ける方が適している場合があります。反対に、同じ三つの項目を何度も文章で繰り返すなら、表にすると差を見つけやすくなります。内容の関係から形式を選びましょう。
今回は三列の表を二つ作る
一つ目は「みどり工房の営業時間」という架空の表です。月曜と火曜は10:00〜17:00、水曜は休業という条件にします。実在の営業案内ではありません。表の列は曜日・開店・閉店の三つで、水曜の休業だけは開店と閉店の二列にまたがるセルにします。
二つ目は三つのコースの時間と料金です。体験30分・1500円、通常60分・2500円、相談20分・500円を練習値にします。これらは商品やサービスの推奨価格ではなく、数値と見出しを対応させるための架空データです。時間と金額の単位を列見出しに入れ、数字だけを見ても何の値か分かるようにします。
※表は横にスクロールできます。
| 練習表 | 行の対象 | 列の項目 | 完成時の確認 |
|---|---|---|---|
| 営業時間 | 月曜・火曜・水曜 | 曜日・開店・閉店 | 水曜が二つの時刻欄をまとめて休業となる |
| 料金比較 | 体験・通常・相談 | コース・時間(分)・料金(円) | 通常が60分・2500円と読める |
| 両方に共通 | 三つの明細行 | 三列の構造 | captionと行列見出しがある |
実際の料金表を作るなら、税込か税別か、適用期間、追加料金、含まれるサービスなど、読者が判断する条件も必要です。今回の表はHTML練習に必要な項目だけへ絞っています。練習コードの値を、そのまま本番サービスの料金として使わないでください。
空欄の意味を、記号だけで済ませない
表の空白は、未確認、該当なし、無料、休業、未入力など、いろいろな意味に読めます。営業時間の水曜を空欄にすると、休業なのか時刻を入力し忘れたのか分かりません。今回は明確に「休業」と書きます。料金がない場合も、0円と未設定は違うため、意味に合う説明を選びます。
「○」「×」「-」を使う場合は、何を表す記号なのかを表の近くに示します。色だけで利用可否を示すと、色を区別しにくい人や印刷時に意味が伝わりにくいことがあります。短い文字を併用し、表を一部だけ見ても内容が分かるようにしましょう。
列の単位は統一します。30分と1時間を同じ列へそのまま混在させるより、「時間(分)」という見出しで30と60にそろえる方が比較しやすくなります。金額も、1500と2,500のように桁区切りの表記が混ざっていたら、表示上のルールを決めます。HTMLを書く前にデータを整えるだけで、後のコードが単純になります。
見出しとデータを分け、動かせるHTMLを作る

table・tr・th・tdの親子関係を理解する
tableは表全体の器、trは一行、thとtdはその行のセルです。見出しのセルにはth、通常のデータにはtdを使います。thが太字で見えることがあるのは既定の表示の一例であり、単に文字を太くするための要素として使うものではありません。
theadは見出しの行のまとまり、tbodyは本文の行のまとまり、tfootは合計やまとめなどの行のまとまりとして使えます。どの表にも合計が必要なわけではないため、今回の表ではtfootを必須にしません。コースの料金を全部足した合計は、利用者が一つのコースを選ぶ表では意味が薄いためです。
※表は横にスクロールできます。
| 要素・属性 | 主な役割 | 今回の例 |
|---|---|---|
| table | 表全体をまとめる | 営業時間と料金で別々に用意 |
| caption | 表の題名を示す | みどり工房の営業時間(架空) |
| thead / tbody | 行のまとまりを分ける | 列見出しと三つの明細行 |
| tr | 一行を作る | 月曜の行、通常コースの行 |
| th / td | 見出しセルとデータセル | 曜日名はth、時刻はtd |
| scope | 見出しの対象方向を示す | 列はcol、行はrow |
表の外側に見出しがあっても、captionを付けると表そのものの名前を示せます。W3C WAIの表のアクセシビリティの解説でも、データと見出しの関係を構造で表すことが重視されています。見た目の線だけに頼らず、HTMLの意味を使って整理します。
次のコードを、一つのHTMLファイルとして保存する
以下はそのまま一つのファイルに保存する練習用コードです。テキストエディターで新しいファイルを作り、UTF-8でpractice.htmlなどの名前にします。すでに存在する仕事用のファイルへ上書きしないよう、専用の練習フォルダーを用意してください。ファイル名がpractice.html.txtになっていないことも確認します。
コードにはページの外枠、二つの表、最小限のCSSを含めています。外部の画像、フォント、JavaScript、ネットワークへの送信は必要ありません。ブラウザーで開くと、営業時間と料金の二つの表が縦に並ぶことを期待する構成です。後の章でCSSと結合部分を一つずつ説明します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>HTMLの表の練習</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 24px; font-family: sans-serif; color: #172b3a; }
main { max-width: 900px; margin: auto; }
.table-scroll { max-width: 100%; overflow: auto; margin-bottom: 32px; }
.table-scroll:focus-visible { outline: 3px solid #175dba; outline-offset: 3px; }
table { width: 100%; min-width: 560px; border-collapse: collapse; }
caption { padding: 12px 0; text-align: left; font-weight: 700; }
th, td { border: 1px solid #6b7e8d; padding: 12px; text-align: left; }
thead th { background: #dbeaf5; }
tbody th { background: #f0f5f8; }
.number { text-align: right; font-variant-numeric: tabular-nums; }
</style>
</head>
<body>
<main>
<h1>HTMLの表の練習</h1>
<p>架空の工房を題材にした表示確認用のページです。</p>
<h2 id="hours-heading">営業時間</h2>
<p>表は横にスクロールできます。</p>
<div class="table-scroll" tabindex="0" role="region" aria-labelledby="hours-heading">
<table id="hours">
<caption>みどり工房の営業時間(架空)</caption>
<thead>
<tr><th scope="col">曜日</th><th scope="col">開店</th><th scope="col">閉店</th></tr>
</thead>
<tbody>
<tr><th scope="row">月曜</th><td>10:00</td><td>17:00</td></tr>
<tr><th scope="row">火曜</th><td>10:00</td><td>17:00</td></tr>
<tr><th scope="row">水曜</th><td colspan="2">休業</td></tr>
</tbody>
</table>
</div>
<h2 id="price-heading">料金比較</h2>
<p>表は横にスクロールできます。金額は練習用で、実在の料金ではありません。</p>
<div class="table-scroll" tabindex="0" role="region" aria-labelledby="price-heading">
<table id="prices">
<caption>三つのコースの時間と料金(架空)</caption>
<thead>
<tr><th scope="col">コース</th><th scope="col">時間(分)</th><th scope="col">料金(円)</th></tr>
</thead>
<tbody>
<tr><th scope="row">体験</th><td class="number">30</td><td class="number">1,500</td></tr>
<tr><th scope="row">通常</th><td class="number">60</td><td class="number">2,500</td></tr>
<tr><th scope="row">相談</th><td class="number">20</td><td class="number">500</td></tr>
</tbody>
</table>
</div>
</main>
</body>
</html>
最初は一文字も変更せず保存し、手元のブラウザーで開いてください。表示できたら、通常コースの60分・2500円、水曜の休業という二点を探します。コードを書いた本人が見た目を覚えているかではなく、表の中から必要な値を探せるかを確認すると、読みやすさを判断しやすくなります。
文字化けする場合は、ファイルの保存文字コードとmeta charsetの指定が合っているかを見ます。ファイルを開くアプリがブラウザーではなくテキストエディターになっているだけの場合もあります。HTMLが文字として表示されるときは、拡張子や、文書作成ソフトの形式で保存していないかを確認してください。
行見出しと列見出しにscopeを付ける
列の上にある「曜日」「開店」「閉店」は、その下の値を説明するためscope=”col”を使います。左側の「月曜」「火曜」「水曜」は横方向の値を説明するためscope=”row”を使います。料金表も同様に、体験・通常・相談が行見出し、時間と料金が列見出しです。
たとえば料金表の2500という値は、「通常」という行見出しと「料金(円)」という列見出しに対応します。太字や背景色がなくても、この関係をHTMLで表しておくことが重要です。W3C WAIの行と列の二方向に見出しがある表に、scopeの方向を明示する考え方が説明されています。
scopeを付ければ、すべての複雑な表の読み上げが自動で完璧になるわけではありません。多段見出しや複数のグループを持つ表では、追加の対応付けや構造の見直しが必要になる場合があります。最初は一つの行見出しと単純な列見出しで済む形にし、難しい情報は複数の表に分けることも検討します。
colspanとrowspanで結合するときは、行列の枠を数える

colspanは、横方向に使う列の数
営業時間表の水曜の行には、thの「水曜」と、colspan=”2″を付けたtdの「休業」があります。HTML上のセル要素は二つですが、表の枠としては水曜が一列、休業が二列を使うため、合計三列です。ほかの曜日と同じ三列の表として位置がそろいます。
colspanの値は、追加で何列広げるかではなく、そのセルが占める列数です。colspan=”2″なら二列分なので、普通の一列セルから考えると一列分広がります。ここを取り違え、さらに閉店用の空セルを残すと、水曜だけ余分な列を持つ形になることがあります。
結合した後は、見た目だけでなく、各行が使う列の数を数えます。今回の表では、月曜が1+1+1、火曜も1+1+1、水曜は1+2で、どれも三列分です。ブラウザーは不完全なHTMLを補正して表示することがあるため、見た目が偶然そろっているだけで正しい構造と判断しないようにします。
rowspanは、縦方向の行をまたぐ
rowspanは、一つのセルが複数の行を占める指定です。たとえば土曜を午前と午後に分ける表で、曜日だけ二行にまたがらせる場合に使えます。下は三列の小さな追加例です。先ほどの二つの表とは別に、練習ページのmain内へ追加して確認する想定にします。
<table>
<caption>土曜の時間帯(架空)</caption>
<thead>
<tr>
<th scope="col">曜日</th>
<th scope="col">区分</th>
<th scope="col">営業時間</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="rowgroup" rowspan="2">土曜</th>
<th scope="row">午前</th>
<td>10:00〜12:00</td>
</tr>
<tr>
<th scope="row">午後</th>
<td>13:00〜17:00</td>
</tr>
</tbody>
</table>
二行目には土曜のセルを書きません。一行目のrowspan=”2″が、その場所をすでに使っているためです。二行目へ空のtdを足すと、列がずれる原因になります。rowgroupは行グループを対象にした見出しで、ここでは同じtbodyにある午前と午後の二行を土曜としてまとめる意図です。
追加例にも狭い画面の対策を付けたい場合は、元の例と同じように、説明と識別できる見出しを置き、table-scrollの外枠で囲んでください。コード断片を加えるときは、同じidを何度も使わないことにも注意します。例を全部貼り付けた後は、ページの中に同じIDがないかを確認します。
結合の数が合うか、占有する枠で検算する
結合セルのある表は、各行に書かれたth・tdの個数だけでは列数を判断できません。上の行から引き継ぐrowspanの枠と、その行に新しく書くセルを合わせて数えます。小さな表なら、紙に三列の枠を描いて埋める方法でも確認できます。
※表は横にスクロールできます。
| 行の例 | その行の新しいセル | 前の行から使う枠 | 合計の列数 |
|---|---|---|---|
| 営業時間の月曜 | 1+1+1 | なし | 3列 |
| 営業時間の水曜 | 1+2 | なし | 3列 |
| 土曜の午前 | 曜日1+区分1+時刻1 | なし | 3列 |
| 土曜の午後 | 区分1+時刻1 | 曜日の1列 | 3列 |
同じ値が何度も出るからといって、必ず結合しなければいけないわけではありません。データを並べ替えたり、表計算ソフトへコピーしたりする予定があるなら、各行へ同じ曜日を書く方が扱いやすいこともあります。見た目を短くする利点と、構造を追いやすくする利点を比べて選びます。
結合が多く、どの見出しがどの値へ対応するかを説明しにくいなら、表を二つに分けることも考えます。複雑なセル結合を実装できることより、読者が正しく比較できることが目的です。W3C WAIの不規則な見出しを持つ表の解説も、グループと対応関係を考える参考になります。
HTML属性の意味を、見た目の命令と混同しない
colspanやrowspanは表の構造を変えます。一方、文字を右へそろえる、余白を増やす、背景色を付けるといった見た目の指定はCSSで行います。結合を使って単にセルの幅を広げるのではなく、同じ情報が複数の列や行に対応するかを考えてください。
古いコード例にある属性を、意味が分からないまま追加し続けるのも避けます。現在の要素と属性の役割は、MDNのtable要素やth要素の資料で確認できます。現在は推奨されない見た目の属性を使うより、構造とCSSを分けた方が修正しやすくなります。
CSSで読みやすく整え、狭い画面の横スクロールを用意する

罫線・余白・配置を少ない指定でそろえる
掲載コードではborder-collapse: collapseで隣り合う罫線をまとめ、thとtdへ余白を付けています。すべての文字を中央へそろえるのではなく、通常の文字は左、時間や料金の数値は右へそろえています。数値を比較する表では、桁の位置がそろうと大小を読み取りやすくなります。
料金の列にはnumberというクラスを付けています。これはHTMLに特別な数値型を付ける指定ではなく、CSSの見た目を適用するための名前です。class=”number”を書いたから、1,500という文字列が自動で数値計算に使えるわけではありません。計算や並べ替えを追加する場合は、別にデータの扱いを設計します。
見出しの背景を薄く変えると、行列をたどりやすくなります。ただし背景色だけに意味を持たせず、thやscopeも維持します。色を消した状態や印刷でも関係が分かるかを考えてください。表のスタイルの考え方は、MDNの表のスタイル設定で確認できます。
表だけを横にスクロールできるようにする
小さな画面へ無理に三列を押し込むと、文字が一文字ずつ改行されたり、項目の対応が追いにくくなったりします。今回のコードでは表にmin-width: 560pxを指定し、外側のtable-scrollへoverflow: autoを付けています。必要な幅を保ちながら、はみ出した部分をその外枠の中で横へ動かす考え方です。
560pxはこの練習用データで試すための値で、すべての表の正解ではありません。長いコース名や項目を追加すれば、別の幅が適切になる場合があります。幅を決めるときは、実際の内容が読めるかを見ます。画面サイズの名前だけから、内容に合わない固定値を選ばないようにしましょう。
表の直前には「表は横にスクロールできます」と書き、横に内容が続くことを伝えます。スクロールバーは環境によって常時表示されない場合があるため、見えるはずと決め付けません。スマートフォンでは実際に横へ動かせるか、端の列まで読めるかを試します。
キーボードで外枠へ移動できるようにする
掲載例の外枠にはtabindex=”0″を付け、キーボードでフォーカスを当てられるようにしています。role=”region”とaria-labelledbyを使い、対応する見出しから領域の名前を取る構成です。どの部分を操作しているか分かるよう、focus-visibleで枠線も表示します。
キーボード操作は、Tabで対象の領域へ移り、矢印キーなどで横方向へ動かせるかを手元で確認します。ブラウザーや支援技術との組み合わせにより操作は異なるため、コード上に属性があるだけで十分とせず、必要な利用環境で試してください。今回の検証ではこの実操作は未実施です。
外枠へ名前を付けるaria-labelledbyの値は、実在する見出しのidと一致させます。hours-headingとprice-headingは別々の表の入口です。同じコードを複製するとき、idをそのまま重複させると対応が曖昧になります。表を追加したら、IDと参照先も一組で変更しましょう。
情報を隠して画面幅へ合わせない
小さな画面だからといって、料金や条件の列を非表示にすると、比較に必要な情報がなくなる場合があります。見た目をすっきりさせるために重要な列を消す前に、横スクロール、表の分割、列見出しの短縮、説明の別記などを検討します。どの方法でも、元の意味が残ることが条件です。
文章の折り返しも内容に合わせて考えます。時刻10:00を途中で改行させる必要はありませんが、長い説明文には折り返しが必要です。すべてのセルへwhite-space: nowrapを一括指定すると、長文で表が極端に広がることがあります。必要な列だけへ指定し、長いデータでも確認することが大切です。
画面を拡大する利用者もいます。通常の表示で収まっているだけではなく、文字を大きくした状態でも、内容が重ならず、隠れず、操作できるかを確認します。見た目を元の大きさへ戻すことを相手へ要求するのではなく、拡大しても情報へ到達できる構成を目指します。
保存したコードを検品し、表示と構造の違いを切り分ける

構造チェックは、ブラウザーの見た目と別に行う
今回のコードは、ローカルの構造解析で、表が二つ、captionが二つ、列見出しが各三つ、本文の行が各三つ、行見出しが各三つであることを照合しました。水曜の休業セルがcolspan=”2″を持つこと、通常コースの時間と料金が60と2,500であることも確認しています。
構造解析は、指定した要素や値が存在するかを機械的に確かめる検証です。ブラウザーでの見た目や、スクリーンリーダーでの読み上げを確認したことにはなりません。本稿の検証環境ではブラウザー起動の制約があったため、実表示とキーボードの横スクロールは未検証として残しています。以下の点検表は、読者が手元で行う表示確認にも使えます。
※表は横にスクロールできます。
| 点検の種類 | 確認する内容 | 今回の検証範囲 |
|---|---|---|
| 構造 | 表2・caption2・行列見出しと三列の整合性 | ローカル解析で照合済み |
| 内容 | 水曜休業、通常60分・2500円 | 文字列と属性を照合済み |
| 広い画面 | 文字・罫線・余白が読みやすいか | ブラウザー実表示は未検証 |
| 狭い画面 | ページ全体ではなく表の外枠を横へ動かせるか | 実機とブラウザー操作は未検証 |
| キーボード | 領域へ移動し端の列へ到達できるか | 操作確認は未検証 |
| 支援技術 | 行列の見出しと値の関係が伝わるか | 読み上げテストは未検証 |
完成を判断するときは、構造の成功を表示の成功へ広げて報告しないようにします。案件で特定のブラウザーやスマートフォンが指定されているなら、その条件で確認する必要があります。チェックした環境と、まだ見ていない環境を分けておくと、引き渡し後の認識違いを減らせます。
セルがずれたら、結合と閉じタグを確認する
列が一つ多く見えるときは、colspanで使っている列へ追加のセルを置いていないかを確認します。行がずれるときは、rowspanで前の行から占有されている枠を忘れていないかを見ます。結合を一時的に外して単純な表へ戻し、どの行からずれたかを探すと原因を絞りやすくなります。
閉じタグの欠けや要素の入れ子にも注意します。trの外へtdを直接置く、tableの途中へ無関係な段落を入れるなど、不正な構造があると、ブラウザーが補正して予想と違う位置へ表示する場合があります。見えている順序だけでなく、コードのtable、thead、tbody、tr、th・tdという関係を読み直してください。
自動整形機能で字下げをそろえると、入れ子が見やすくなります。ただし、字下げがきれいでもHTMLの意味が正しいとは限りません。表の行と列を紙へ書き出し、各セルがどの見出しに対応するかを指でたどれるか確認すると、構造上の誤りを見つけやすくなります。
CSSが効かないときは、対象のクラスと保存先を見る
背景色や余白が変わらない場合は、変更したファイルを保存したか、ブラウザーが同じファイルを開いているかを確認します。似た名前のpractice.htmlとpractice-copy.htmlを別々に開いていると、編集したのに反映されないように見えます。まずファイルの場所と名前をそろえましょう。
numberというクラスへ右寄せを指定したなら、目的のtdへclass=”number”が付いているかを見ます。class名の綴り違いや、別のCSSが後から上書きしていることもあります。サイトへ組み込む場合は、テーマのtableやthに関するスタイルが影響するため、単独ファイルでの結果がそのまま一致するとは限りません。
既存サイトへコードを入れるとき、すべてのtableへ一括してCSSを適用すると、ほかの記事の表まで変わる可能性があります。実務では、対象の表へ固有のクラスを付け、その範囲に限定したセレクターを使う設計を検討します。本稿のCSSは独立した練習ファイル全体を対象としており、本番サイト全体へそのまま貼る前提ではありません。
値を一行追加し、更新しても崩れないか確かめる
完成後の追加練習として、料金表に新しい架空コースを一行足してみます。既存の行をコピーする場合は、コース名、時間、料金をすべて見直し、行見出しのscopeを残してください。同じコース名だけ変えて料金を直し忘れると、構造は正しくても内容が誤ります。
長い名前を入れた行、0に相当する値ではなく「対象外」と示す行など、実際に起こりそうな内容でも確認します。新しい行を足しても三列の関係が保たれ、captionと列見出しの意味が変わっていないことを見ます。項目が増えた結果、説明が足りなくなった場合は、表そのものの設計を見直してください。
数字を更新する際は、表示の桁区切りや単位も確認します。料金表で「2500円」を「2.500円」と誤って書くと、地域や読み方によって意味が曖昧になる場合があります。値、単位、表記の三つを一緒に点検すると、HTMLのタグだけを確認するより実務的な検品になります。
説明できる小さな表を、制作実績へつなげる
練習成果を残すなら、HTMLとCSSだけでなく、表にした理由、見出しの対応、セル結合の数え方、未検証の環境もメモにします。「きれいな表を作った」という説明より、どの情報をどう整理し、どんな確認をしたかが伝わります。コードを公開する場合は、架空データだけであることと、外部素材の権利を確認してください。
作品のまとめ方は、プログラミングのポートフォリオとREADMEの作り方も参考になります。学習支援を比較したい場合は、副業向けプログラミングスクールの比較で、費用や支援内容を確認する方法もあります。スクール受講や表の制作だけで、仕事の獲得や収入が保証されるわけではありません。
HTMLテーブルは、行と列の意味を決め、見出しを付け、結合を検算し、必要な幅と操作方法を用意すると扱いやすくなります。まずは二つの小さな表を自分の環境で開き、コード上の構造と読者が見た結果を別々に確かめるところまで進めてみてください。