データテーブルとは

複数の項目を行と列に整理して並べ、見出しクリックで並び替えもできる表。

概要

複数の項目を行(レコード)と列(項目)に整理して並べる表です。会員一覧や注文一覧のように、同じ形式の データをたくさん見せたいときに使われます。列見出しをクリックすると昇順・降順に並び替えられる「ソート可能 テーブル」もよく実装されます。

別名

表記ゆれ:データテーブル、テーブル、一覧表、ソート可能テーブル、sortable

「テーブル」だけだと HTML の <table> 要素そのものを指すこともあるため、ソートや横スクロールなどの 機能を含めて話したいときは「データテーブル」と呼ぶと伝わりやすくなります。

AI への伝え方

「データテーブルを作って。列見出しをクリックしたら並び替えできるようにして」のように、ソート機能の 要否を伝えます。列数が多くスマホ幅で収まらない場合は「横スクロールできるようにして」と伝えると、表を 専用の枠で囲んで横スクロールさせる実装になります。行の背景を 1 行おきに変える「ゼブラ表示」の要否も 伝えておくと仕様がぶれません。

デモ

佐藤 34 営業
鈴木 28 開発
高橋 41 総務
田中 25 開発

コード

HTML
<div class="data-table-demo">
  <div class="data-table-demo__scroll">
    <table class="data-table-demo__table">
      <thead>
        <tr>
          <th scope="col" aria-sort="none">
            <button class="data-table-demo__sort" type="button" data-key="0" data-type="string">名前</button>
          </th>
          <th scope="col" aria-sort="none">
            <button class="data-table-demo__sort" type="button" data-key="1" data-type="number">年齢</button>
          </th>
          <th scope="col" aria-sort="none">
            <button class="data-table-demo__sort" type="button" data-key="2" data-type="string">部署</button>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>佐藤</td>
          <td>34</td>
          <td>営業</td>
        </tr>
        <tr>
          <td>鈴木</td>
          <td>28</td>
          <td>開発</td>
        </tr>
        <tr>
          <td>高橋</td>
          <td>41</td>
          <td>総務</td>
        </tr>
        <tr>
          <td>田中</td>
          <td>25</td>
          <td>開発</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>
CSS
.data-table-demo {
  font-family: sans-serif;
  font-size: 13px;
  color: #333333;
}

.data-table-demo__scroll {
  overflow-x: auto;
}

.data-table-demo__table {
  width: 100%;
  min-width: 320px;
  border-collapse: collapse;
}

.data-table-demo__table th,
.data-table-demo__table td {
  padding: 8px 12px;
  border-bottom: 1px solid #e6e6e6;
  text-align: left;
  white-space: nowrap;
}

.data-table-demo__table thead th {
  padding: 0;
  background: #f2f2f2;
}

.data-table-demo__sort {
  width: 100%;
  padding: 8px 12px;
  border: 0;
  background: transparent;
  color: #333333;
  font: inherit;
  font-weight: bold;
  text-align: left;
  cursor: pointer;
}

.data-table-demo__sort:hover {
  background: #e6e6e6;
}

.data-table-demo__table th[aria-sort="ascending"] .data-table-demo__sort::after {
  content: " ▲";
  color: #0017c1;
}

.data-table-demo__table th[aria-sort="descending"] .data-table-demo__sort::after {
  content: " ▼";
  color: #0017c1;
}

.data-table-demo__table tbody tr:nth-child(even) {
  background: #f2f2f2;
}
JS
const table = document.querySelector(".data-table-demo__table");
const tbody = table.querySelector("tbody");
const buttons = table.querySelectorAll(".data-table-demo__sort");

let currentKey = null;
let currentDirection = "asc";

function getCellValue(row, index) {
  return row.children[index].textContent.trim();
}

buttons.forEach((button) => {
  button.addEventListener("click", () => {
    const index = Number(button.dataset.key);
    const type = button.dataset.type;
    const direction = currentKey === index && currentDirection === "asc" ? "desc" : "asc";
    currentKey = index;
    currentDirection = direction;

    const rows = Array.from(tbody.querySelectorAll("tr"));
    rows.sort((a, b) => {
      const valueA = getCellValue(a, index);
      const valueB = getCellValue(b, index);
      const comparison =
        type === "number" ? Number(valueA) - Number(valueB) : valueA.localeCompare(valueB, "ja");
      return direction === "asc" ? comparison : -comparison;
    });
    rows.forEach((row) => tbody.appendChild(row));

    table.querySelectorAll("th").forEach((th) => th.setAttribute("aria-sort", "none"));
    button.closest("th").setAttribute("aria-sort", direction === "asc" ? "ascending" : "descending");
  });
});