テーブル
component列ごとの項目名を上に並べて、複数のデータを行で比較するテーブルです。1 件の詳細を縦に並べて見せるときはシンプルテーブルを使います。
基本
項目名の行(thead)が上、データ行(tbody)が下。値が無いセルは — を text-fg-middle で表示(10.35:1、WCAG AAA)
| Item | Name | Description | Component | Optional |
|---|---|---|---|---|
| 1 | Container | コンテナ要素 | — | |
| 2 | Header | ヘッダー要素 | — | |
| 3 | Body | ボディ要素 | — |
使用例 — td 内に任意の UI
セルにはバッジやリンク、ボタンも入れられる
| 案件名 | ステータス | 担当者 | 詳細 |
|---|---|---|---|
| Aプロジェクト | 公開中 | 山田 太郎 | 詳細を見る(Aプロジェクト) |
| Bプロジェクト | 審査中 | 佐藤 花子 | 詳細を見る(Bプロジェクト) |
| Cプロジェクト | 下書き | — | 詳細を見る(Cプロジェクト) |
列幅 — 締める列だけ指定し、自由文に余白を渡す
固定書式の列(日付・状態・金額・操作)は data-table-fit(数値は data-table-num)を th と同列の td 全てに付けて内容幅に締める。自由文は data-table-text の 1 列。列が多く収まらないときは data-table-wrap(tabindex="0")で包んで横スクロール — 列幅を詰めて無理に収めない
| 契約名 | 概要 | 金額 | 更新日 | 状態 | 操作 |
|---|---|---|---|---|---|
| 本社ビル賃貸借契約 | 本社ビル 3〜5 階の賃貸借。更新は 2 年ごとで、次回更新時に面積の見直しを予定。 | 1,200,000円 | 2026/09/01 | 有効 | 詳細を見る(本社ビル賃貸借契約) |
| 清掃業務委託契約 | 共用部の日常清掃と月 1 回の定期清掃。 | 85,000円 | 2026/03/31 | 期限切れ | 詳細を見る(清掃業務委託契約) |
| 会計ソフト利用契約 | クラウド会計の年間ライセンス。5 ユーザーまで。 | 96,000円 | 2026/12/15 | 審査中 | 詳細を見る(会計ソフト利用契約) |
<div class="data-table-wrap" tabindex="0"> <table class="data-table"> <thead><tr> <th scope="col">契約名</th> <!-- 幅指定なし: 余白を吸う --> <th scope="col" class="data-table-text">概要</th> <!-- 自由文: 折り返し + 最小幅 --> <th scope="col" class="data-table-num">金額</th> <!-- 数値: 右寄せ + 等幅数字 --> <th scope="col" class="data-table-fit">状態</th> <!-- 固定書式: 内容幅に締める --> </tr></thead> <tbody><tr> <td>本社ビル賃貸借契約</td> <td class="data-table-text">…</td> <td class="data-table-num">1,200,000円</td> <td class="data-table-fit"><span class="badge badge-soft-success">有効</span></td> </tr></tbody> </table> </div>
見出し行 — タイトルと、件数・表全体への操作
data-table-header の 1 段目にタイトル、2 段目(data-table-toolbar)の左に表示件数、右に「検索 → アイコンの操作(3 個まで)→ 主操作」を sm でそろえて並べる。検索は虫眼鏡か Enter で確定してから絞り込み(入力のたびには変えない)、絞り込みのパネルには複数の列や列に無い条件(振込月)をまとめる。適用中の条件は操作の行の下にタグで並べ、× で 1 つずつ、「すべて解除」でまとめて解除できる。1 列の値だけで絞るなら列フィルター、並べ替えは列見出し
振込一覧
振込予約と実績
| 状態 | ||||
|---|---|---|---|---|
| 2026/09/30 | 株式会社みどり商事 | 1,200,000円 | 予約 | 山田 |
| 2026/09/30 | さくら会計事務所 | 110,000円 | 予約 | 佐藤 |
| 2026/09/25 | 株式会社みどり商事 | 1,200,000円 | 完了 | 山田 |
| 2026/09/24 | 山田工務店 | 385,000円 | 完了 | 佐藤 |
| 2026/09/22 | ひかり印刷 | 52,800円 | 失敗 | 山田 |
| 2026/08/29 | 山田工務店 | 420,000円 | 完了 | 鈴木 |
| 2026/08/25 | ひかり印刷 | 48,400円 | 完了 | 佐藤 |
| 2026/08/20 | 株式会社あおば | 2,750,000円 | 完了 | 鈴木 |
| 条件に合うデータがありません | ||||
<div class="data-table-header"> <div class="data-table-heading"> <h2 class="data-table-title" id="transfers-title">振込一覧</h2> <p class="data-table-subtitle">振込予約と実績</p> </div> <div class="data-table-toolbar"> <p class="data-table-count" aria-live="polite">全 8 件</p> <div class="data-table-actions" role="group" aria-label="振込一覧の操作"> <form role="search" aria-label="振込一覧の検索"> <div class="search-input search-input-sm"> <input type="search" class="search-input-field" aria-label="検索キーワード">…クリア… <button type="submit" class="search-input-submit-icon" aria-label="検索">…</button> </div> </form> <span class="tooltip"> <button type="button" class="icon-btn icon-btn-sm icon-btn-neutral icon-btn-ghost data-table-filter-trigger" aria-labelledby="tip-filter" aria-haspopup="dialog" aria-expanded="false" popovertarget="transfers-filter">…</button> <span class="tooltip-content" id="tip-filter" role="tooltip">絞り込む</span> </span> …(アイコンの操作は 3 個まで。4 個目からは more-horizontal の action-menu) <button type="button" class="btn btn-sm btn-primary btn-solid">新規登録</button> </div> </div> <div class="data-table-conditions" hidden> <!-- 適用中の条件。条件が無いときは hidden --> <ul class="tag-list" aria-label="適用中の絞り込み条件"> <li class="tag"> <span class="tag-label">キーワード: 山田</span> <button type="button" class="tag-remove" aria-label="『キーワード: 山田』の絞り込みを解除">…</button> </li> </ul> <button type="button" class="btn btn-sm btn-neutral btn-ghost">すべて解除</button> </div> </div> <table class="data-table" aria-labelledby="transfers-title">…</table> <div class="data-table-filter-panel data-table-filter-panel-end" id="transfers-filter" popover role="dialog" aria-label="振込一覧を絞り込む"> <div class="data-table-filter-field"><label for="f-month">振込月</label><div class="selector selector-sm">…</div></div> … </div>
列フィルター — 列の値で行を絞り込む
見出しの横の data-table-filter-trigger(icon-btn)から Popover を開き、select で値を選んで「適用」で確定する。「リセット」はその列の絞り込みを解除。複数の列の絞り込みは掛け合わせ(AND)。件数は aria-live="polite" で告知し、0 件でも表は消さずに「条件に合うデータがありません」の行を出す
全 8 件
| 氏名 | 役割 | 部署 | 最終ログイン |
|---|---|---|---|
| 山田 太郎 | 管理者 | 営業 | 2026/09/24 |
| 佐藤 花子 | 編集者 | 開発 | 2026/09/25 |
| 鈴木 一郎 | 閲覧者 | 営業 | 2026/09/20 |
| 高橋 美咲 | 編集者 | 人事 | 2026/09/22 |
| 田中 健 | 管理者 | 開発 | 2026/09/25 |
| 伊藤 さくら | 閲覧者 | 人事 | 2026/08/30 |
| 渡辺 翔 | 編集者 | 営業 | 2026/09/18 |
| 中村 結衣 | 閲覧者 | 開発 | 2026/09/12 |
| 条件に合うデータがありません | |||
<th scope="col" class="data-table-fit"> <span class="data-table-filter"> <span id="col-role">役割</span> <button type="button" class="icon-btn icon-btn-sm icon-btn-neutral icon-btn-ghost data-table-filter-trigger" aria-label="役割で絞り込む" aria-haspopup="dialog" aria-expanded="false" popovertarget="filter-role"> <svg class="icon icon-sm" aria-hidden="true"><use href="./icons.svg#lucide-filter"></use></svg> </button> </span> </th> … <!-- パネルは表の直後に置く(Tab 順はトリガーの直後になる) --> <div class="data-table-filter-panel" id="filter-role" popover role="dialog" aria-label="役割で絞り込む"> <div class="selector selector-sm"> <select class="select" aria-labelledby="col-role"> <option value="">すべて</option><option>管理者</option><option>編集者</option> </select> </div> <div class="data-table-filter-actions"> <button type="button" class="btn btn-sm btn-neutral btn-ghost" data-filter-reset>リセット</button> <button type="button" class="btn btn-sm btn-primary btn-solid" data-filter-apply>適用</button> </div> </div>
列フィルターの状態
絞り込み中の列はトリガーに data-filtered を付ける(アイコンが primary になり、右上にドットが付く)。aria-label にも適用中の値を含める。右は開いた状態のパネル(is-static-preview)
| 役割 | 部署 |
|---|---|
| 通常 | 絞り込み中 |
並べ替え — 列見出しを押して行を並べ替える
見出しの文字ごと data-table-sort のボタンにし、並べ替え中の th にだけ aria-sort を付ける。1 回で昇順、もう一度で降順。金額・日付・よみがなど表示と並び順が違う列は td に data-sort-value(契約名はよみがなで並ぶ)。結果は aria-live="polite" で告知する
| 状態 | |||
|---|---|---|---|
| 本社ビル賃貸借契約 | 1,200,000円 | 2026/09/01 | 有効 |
| 清掃業務委託契約 | 85,000円 | 2026/03/31 | 期限切れ |
| 会計ソフト利用契約 | 96,000円 | 2026/12/15 | 審査中 |
| 複合機リース契約 | 420,000円 | 2027/01/10 | 有効 |
| 警備業務委託契約 | 310,000円 | 2026/06/30 | 有効 |
<th scope="col" class="data-table-fit" aria-sort="ascending"> <!-- 並べ替え中の列にだけ付ける --> <button type="button" class="data-table-sort"> 更新日 <svg class="icon icon-sm data-table-sort-icon" aria-hidden="true"><use href="./icons.svg#lucide-chevron-up"></use></svg> </button> </th> … <td class="data-table-num" data-sort-value="1200000">1,200,000円</td> <!-- 表示と並び順が違う値 --> … <p class="sr-only" aria-live="polite"></p> <!-- 「更新日の昇順で並べ替えました」を告知 -->
並べ替えの状態
並べ替えていない列は chevrons-up-down(fg/low)、昇順は chevron-up、降順は chevron-down(primary/700)。色だけでなく形で状態を示す
| 並べ替えていない |
| 昇順 |
| 降順 |
機能
このコンポーネントの用途
列見出しを上に置き、複数データを行で比較する表。
1 件の詳細を縦に並べるなら data-table ではなく simple-table。
列の値で行を絞り込むときは見出しに列フィルター(data-table-filter)、行を並べ替えるときは見出しをボタン(data-table-sort)にする。
表の上に並べて一覧全体を 1 つの軸で切り替えるなら tab、複数条件を同時に掛け合わせるトグルなら filter-chip。
表のタイトルと、表全体に効く操作(検索・複数条件の絞り込み・書き出し・新規作成)は、表の上の見出し行
(data-table-header)にまとめる(タイトルの下の行に、左に表示件数・右に操作)。1 列の値で絞るなら列フィルター。
並べ替えは列見出しのボタンと、見出し行の並べ替えのパネルの両方から行え、状態は連動する。
使用法
推奨される使い方と、やりがちな NG パターン
OK
- ✓thead に列見出し、tbody に行データを置く。
- ✓固定書式の列(日付・ID・状態バッジ・金額・操作)は th と同列の td 全てに data-table-fit (数値は data-table-num)を付けて内容幅に締める。自由文の列は data-table-text で 1 列だけ。
- ✓列が多く親幅に収まらない表は <div class="data-table-wrap" tabindex="0"> で包んで横スクロール。 列幅を詰めたり文字を小さくして 1 画面に収めない(本体サイトも「自然な列幅のまま横スクロール」が方針)。
- ✓並べ替えられる列は見出しの文字ごと data-table-sort のボタンにし、並べ替え中の th にだけ aria-sort(ascending / descending)を付ける。
- ✓1 回押すと昇順、もう一度押すと降順。別の列を押したらその列の昇順に切り替え、前の列の aria-sort は外す。
- ✓金額・日付・よみがなど、表示の文字と並び順が違う列は td に data-sort-value を持たせる。
- ✓列フィルターはその列の値(役割・部署・状態など選択肢が決まっている列)で絞り込む。選択肢の先頭は「すべて」。
- ✓列フィルターの select は selector-sm で包み、ボタン(btn-sm)と高さを揃える。
- ✓列フィルターは選んだだけでは絞り込まず、「適用」で確定する。「リセット」はその列の絞り込みを解除して閉じる。
- ✓絞り込み中の列はトリガーに data-filtered を付け、aria-label に適用中の値を含める(例: 役割で絞り込む(適用中: 管理者))。
- ✓表の近くに「全 8 件中 3 件を表示」のような件数を置き、aria-live="polite" で絞り込みの結果を伝える。 0 件になったら表を消さず、tbody に「条件に合うデータがありません」の行を出す。
- ✓表のタイトルと表全体への操作は data-table-header にまとめる。1 段目はタイトル(と補足)だけ、2 段目の data-table-toolbar の左に表示件数(data-table-count)、右に操作を置く(件数と操作が同じ行で、結果と操作の対応が分かる)。
- ✓見出し行の操作は左から「検索 → アイコンの操作 → 主操作(btn-primary 1 つ)」の順に並べ、すべて sm(32)でそろえる。
- ✓アイコンだけの操作は tooltip で包み、吹き出しを aria-labelledby で名前にする(見える名前と読み上げを一致させる)。
- ✓アイコンの操作は 3 個まで。4 個目からは more-horizontal の icon-btn + action-menu(.action-menu-end)に畳む。
- ✓検索は <form role="search"> で包んだ search-input-sm を入力欄のまま置き、虫眼鏡の送信ボタン(search-input-submit-icon)か Enter で確定してから行を絞り込む(件数を aria-live で告知)。クリア(×)を押したら全件に戻す。
- ✓見出し行の絞り込みは、複数の列を掛け合わせる条件や、列に無い条件(振込月など)をパネルにまとめるときに使う。 パネルは data-table-filter-panel、条件ごとに data-table-filter-field(label + selector-sm)。
- ✓見出し行の絞り込み・並べ替えのパネルには data-table-filter-panel-end を付け、トリガーの右端に揃えて開く (操作は表の右端に並ぶため。左端揃えだとパネルが画面の外へはみ出し、トリガーから離れた位置に押し戻される)。
- ✓ページの見出しがそのまま表の名前なら、data-table-title をページの h1 にしてよい(同じ名前の見出しを 2 つ重ねない)。
- ✓検索・絞り込みを適用したら、操作の行の下の data-table-conditions に条件をタグで並べる(「キーワード: 山田」 「振込月: 2026 年 9 月」)。タグの × でその条件だけ、「すべて解除」で全部を解除できるようにする。
- ✓条件のタグの削除ボタンの aria-label は「『振込月: 2026 年 9 月』の絞り込みを解除」のように条件を含める。
- ✓見出し行に並べ替えを置くときは、chevrons-up-down の icon-btn から data-table-filter-panel を開き、「並べ替える列」と 「順序(昇順・降順)」を選んで「適用」する。「リセット」は登録順に戻す。action-menu にしない(値を選ぶ操作のため)。
- ✓見出し行の並べ替えと列見出しの data-table-sort は状態を連動させる(th の aria-sort・アイコン・トリガーの「適用中」)。
NG
- ✕1 件の項目一覧(キー/値)に使わない → simple-table。
- ✕罫線・余白を直書きしない → .data-table の既定を使う。
- ✕全列に w-1/4 等の幅を配らない → 合計が崩れて折り返し・溢れの原因。幅は「締める列」だけ data-table-fit / data-table-num で指定し、残りは自動幅に任せる。
- ✕全セルに whitespace-nowrap を付けない → 表が親幅を越えて溢れる。nowrap は fit / num 列だけ。
- ✕状態バッジ・日付・金額の列を幅指定なしで置かない → 自由文に押されて 2 行に折れる → data-table-fit。
- ✕並べ替えを表の外(select・tab・見出し行のパネル)だけで作らない → 列見出しでも並べ替えられるようにし(data-table-sort)、 どの列で並んでいるかを列見出しの aria-sort とアイコンで示す。見出し行のパネルを置くなら状態を連動させる。
- ✕並べ替えのボタンをアイコンだけにしない → 見出しの文字ごとボタンにし、押せる範囲を広く取る(名前も見出しの文字になる)。
- ✕並べ替えていない列に aria-sort="none" を付けて回らない → 並べ替え中の列だけに付ける。
- ✕列フィルターのパネルに action-menu を使わない → action-menu は操作の実行用。値を選ぶのは data-table-filter-panel + select。
- ✕フィルターのトリガーを見出しから離して表の外に置かない → どの列の絞り込みか分からなくなる。
- ✕絞り込み中を色だけで示さない → data-filtered のドットと aria-label の「適用中」を併せて使う。
- ✕検索を入力のたびに絞り込まない → 1 文字ごとに行と件数の告知が変わり、スクリーンリーダーでは入力中の文字と告知が重なる (入力の途中で表が動くのは予期しない変化でもある。WCAG 3.2.2)。
- ✕適用中の条件をパネルの中だけに閉じ込めない → パネルを開かないと何で絞っているか分からない。
- ✕1 列の値だけで絞るフィルターを見出し行に置かない → 列フィルター(どの列の絞り込みか分かる)。
- ✕行ごとの操作(編集・削除)を見出し行に置かない → 行の操作列。
- ✕検索をアイコンだけにして、押すと入力欄が広がる形にしない → 入力できることが見えず、開閉のフォーカス管理も要る。
- ✕主操作(btn-primary solid)を 2 つ以上置かない → 1 つに絞り、他は outline / ghost か action-menu へ。 分割ボタン(btn-group)にするなら btn-group の規則どおり outline でつなげる。
- ✕見出し行と表を card で包まない → 枠と余白が二重になる(テーブルだけのものは card にしない)。
複数件の比較は table・1 件の詳細は simple-table
用途の使い分け| 案件 | 状態 |
|---|---|
| A | 公開中 |
| B | 審査中 |
複数件を列で比較するときは data-table(thead の列見出し)。
<table class="data-table"> <thead><tr><th scope="col">案件</th>…</tr></thead> </table>
| 項目 | 値 |
|---|---|
| プロジェクト名 | A |
| 担当者 | 山田 |
1 件の属性を縦に並べるのに data-table を使わない → simple-table(行見出し)が適切。
<!-- 1 件の詳細は simple-table を使う --> <table class="simple-table"><tr><th scope="row">担当者</th><td>山田</td></tr></table>
列幅は「締める列」だけ指定する
レイアウト崩れの防止| 案件 | 金額 | 状態 |
|---|---|---|
| 老舗旅館の事業譲渡(温泉街・客室 12) | 35,000,000円 | 公開中 |
| 製麺所 | 8,500,000円 | 交渉中 |
状態は data-table-fit、金額は data-table-num で内容幅に締め、案件名(幅指定なし)が余白を吸う。バッジも金額も折れない。
<th scope="col">案件</th> <th scope="col" class="data-table-num">金額</th> <th scope="col" class="data-table-fit">状態</th>
| 案件 | 金額 | 状態 |
|---|---|---|
| 老舗旅館の事業譲渡(温泉街・客室 12) | 35,000,000円 | 公開中 |
| 製麺所 | 8,500,000円 | 交渉中 |
全列に w-1/3 を配ると案件名が数文字で折れ、金額・状態には余白だけが残る。全セル whitespace-nowrap は逆に親幅を突き抜ける。収まらない幅は data-table-wrap で横スクロールへ。
<!-- 全列に幅を配らない・全セルに nowrap を付けない --> <th class="w-1/3">案件</th><th class="w-1/3">金額</th>…
テーブルのアクセシビリティ
ネイティブ <table> ベース。列見出しに <th scope="col">、表の名前に <caption>、空セルは —、幅広なら横スクロールで reflow を担保します。WCAG 2.2 全基準は アクセシビリティ ページを参照。
期待される配慮
Presentation / Adaptability / Interaction / Assistive Technology
見出し行とデータ行を視覚的に区別。空セルは空白でなく —(fg/middle・AAA)で「値なし」を明示。
幅が足りない時は横スクロールで内容を保持(reflow)。文字は rem で拡大に追従。
横スクロール領域はキーボードでもスクロールできるようにする(フォーカス可能なコンテナ等)。
<th scope="col"> で列とデータの対応が読まれる。<caption> か aria-label で表に名前を付ける。
DS が担保する機能
追加実装なしで満たされる
- ✓ネイティブ
<table>/thead/tbodyのセマンティック構造 - ✓見出し行とデータ行のコントラスト・区切りを確保
- ✓空セルの
—は fg/middle(10.35:1・AAA)で可読 - ✓カタログでは狭幅時に
card-bodyが横スクロール(reflow の下地)
実装時の必須対応
プロダクト側で担保する
- !列見出しを
<th scope="col">にする(データセルはtd) - !表に
<caption>かaria-labelで名前を付ける - !空セルは空白にせず
—(またはテキスト)で「値なし」を示す - !幅広なテーブルは
overflow-x: autoのコンテナで包み、キーボードでもスクロールできるようにする - !並べ替えは見出しの文字ごと
<button>にし、並べ替え中の th にだけaria-sort(ascending / descending)を付ける。結果はaria-live="polite"で告知する - !列フィルターのトリガーに
aria-label(「役割で絞り込む」)・aria-haspopup="dialog"・aria-expandedを付け、絞り込み中は aria-label に適用中の値を含める - !列フィルターのパネルは
role="dialog"と aria-label。開いたら select へフォーカスし、閉じたらトリガーへ戻す - !絞り込み結果の件数を
aria-live="polite"の領域で告知する(行が消えたことは画面を見ないと分からない)
テスト方法
スクリーンリーダー / 拡大・リフロー / 自動チェック
- SR各セルで対応する列見出しが読まれる(「状態、公開中」等)。表の名前が読まれる
- SR列フィルターを適用すると「全 8 件中 3 件を表示」が読まれる。トリガーは「役割で絞り込む(適用中: 管理者)」と読まれる
- SR並べ替えの見出しは「更新日、ボタン、昇順で並べ替え済み」のように読まれ、押すと「更新日の降順で並べ替えました」と告知される
- キートリガーで Enter / Space → select にフォーカス、Tab で「リセット」「適用」、Esc で閉じてトリガーに戻る
- 拡大320px 幅・200% 拡大で、横スクロールを許容しつつ情報が欠けない
- 自動axe / Lighthouse で
th/scope・caption・コントラストを確認
列見出しは th scope="col"・空セルは —
達成基準:WCAG 1.3.1 情報及び関係性
| 案件 | 担当 |
|---|---|
| A | 山田 |
| B | — |
th scope="col"、caption で表名、空セルは —。
<table class="data-table"> <caption class="sr-only">案件一覧</caption> <thead><tr><th scope="col">案件</th>…</tr></thead> <tbody><tr><td>B</td><td>—</td></tr></tbody> </table>
| 案件 | 担当 |
| A | 山田 |
| B |
見出しを td にすると列との対応が伝わらず、空セルも空白だと「値なし」か不明。
<thead><tr><td>案件</td>…</tr></thead> <!-- th/scope なし --> <tr><td>B</td><td></td></tr> <!-- 空白 -->