ソート可能なテーブル見出しSortable table header
表の列見出しを押すと、その列で行を並べ替えられるもの。見出しの横の三角で、どの列をどちら向きに並べているかを示す。
表示例
| りんご | 120円 | 8 |
|---|---|---|
| みかん | 180円 | 3 |
| ぶどう | 450円 | 5 |
アイコン: Font Awesome 7 Free(fa-solid / fa-regular)
別名・英語表記
- ソートボタン
- 並べ替え見出し
- 並び替えボタン
- ソート列
- 昇順・降順の切り替え
- ソート可能な表
- Sortable table header
実装例
<table class="x-sortable-header">
<caption class="x-sortable-header__c">果物の価格(見出しを押すと並べ替え)</caption>
<thead>
<tr>
<th scope="col">
<button class="x-sortable-header__b" type="button">
品名<i class="fa-solid fa-sort" aria-hidden="true"></i>
</button>
</th>
<!-- aria-sort を付けるのは、いま並べ替えている1列だけ -->
<th scope="col" aria-sort="ascending" class="x-sortable-header__on">
<button class="x-sortable-header__b" type="button">
価格<i class="fa-solid fa-sort-up" aria-hidden="true"></i>
</button>
</th>
<th scope="col">
<button class="x-sortable-header__b" type="button">
在庫<i class="fa-solid fa-sort" aria-hidden="true"></i>
</button>
</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">りんご</th><td>120円</td><td>8</td></tr>
<tr><th scope="row">みかん</th><td>180円</td><td>3</td></tr>
<tr><th scope="row">ぶどう</th><td>450円</td><td>5</td></tr>
</tbody>
</table>
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-sortable-header,
.x-sortable-header *:not([class*="fa-"]),
.x-sortable-header *:not([class*="fa-"])::before,
.x-sortable-header *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-sortable-header {
width: 270px;
border-collapse: collapse;
background: #fff;
color: #1b2028;
font-size: 12px;
}
.x-sortable-header__c {
margin-bottom: 6px;
color: #5b6472;
font-size: 11px;
text-align: left;
}
.x-sortable-header th,
.x-sortable-header td {
padding: 7px 10px;
border: 1px solid #e3e6ea;
text-align: left;
}
.x-sortable-header td {
text-align: right;
font-variant-numeric: tabular-nums;
}
/* 見出しのセルは余白を持たず、中のボタンがセルいっぱいに広がる(押せる範囲を広く取る) */
.x-sortable-header thead th {
padding: 0;
background: #f1f3f6;
}
.x-sortable-header__b {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
width: 100%;
min-height: 34px;
padding: 0 10px;
border: 0;
background: none;
color: #1b2028;
font: inherit;
font-size: 11.5px;
font-weight: 700;
cursor: pointer;
}
.x-sortable-header__b .fa-solid {
width: auto;
color: #5b6472;
}
.x-sortable-header__b:hover {
background: #e3e8ef;
}
.x-sortable-header__b:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
/* 並べ替え中の列。向きは矢印の形で示し、色と下線は補助にする */
.x-sortable-header__on {
box-shadow: inset 0 -2px 0 #1d4ed8;
}
.x-sortable-header__on .x-sortable-header__b .fa-solid {
color: #1d4ed8;
}
AIへの指示文
「ソート可能なテーブル見出し(Sortable table header)」を実装してください。
## これは何か
表の列見出しを押すと、その列で行を並べ替えられるもの。見出しの横の三角で、どの列をどちら向きに並べているかを示す。
## 使いどころ
- 価格・日付・件数のように、利用者ごとに見たい順番が違う表
- 行数が多く、上から読むより並べ替えて探したほうが速いとき
## 実装の要件
- table > caption + thead > tr > th[scope=col][aria-sort] > button(列名 + 向きのアイコン)
- 並べ替えていない列には上下両向きのアイコン、並べ替えている列には片向きのアイコンを出す
- ボタンはセルいっぱいに広げ、押せる範囲を広く取る
- 押せる見出しと押せない見出しを見た目で区別する。全部の列に並べ替えを付ける必要はない
- 数値は数として比べる。文字列のまま比べると「1,000円」が「200円」より前に来る
- 漢字の名前は、文字コードの順に並べても読みの順にならない。並べ替え用に読み(ふりがな)を別に持つ
- ページ送りのある表は、全件を並べ替えてから切り分ける。いま見えている1ページだけを並べ替えない
- 押すたびに「昇順 → 降順」を繰り返すのか、「元の順」に戻す段階を挟むのかを決め、サイト内の表で揃える
## アクセシビリティの要件
- 見出しのセル th の中に button を置く。th そのものにクリックを付けると、キーボードで届かず、押せることも伝わらない
- 並べ替えている列の th に aria-sort="ascending" か "descending" を付ける。付けるのは同時に1列だけで、他の列からは外す
- 向きは三角や矢印の形で示し、色だけに頼らない。アイコンは aria-hidden="true" にして、向きは aria-sort で伝える
- aria-sort の変化を読み上げない環境があるので、並べ替えたあとに「価格の安い順に並べ替えました」を aria-live="polite" で伝えると確実
- 並べ替えたあとも、フォーカスは押した見出しのボタンに残す
## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。
```html
<table class="x-sortable-header">
<caption class="x-sortable-header__c">果物の価格(見出しを押すと並べ替え)</caption>
<thead>
<tr>
<th scope="col">
<button class="x-sortable-header__b" type="button">
品名<i class="fa-solid fa-sort" aria-hidden="true"></i>
</button>
</th>
<!-- aria-sort を付けるのは、いま並べ替えている1列だけ -->
<th scope="col" aria-sort="ascending" class="x-sortable-header__on">
<button class="x-sortable-header__b" type="button">
価格<i class="fa-solid fa-sort-up" aria-hidden="true"></i>
</button>
</th>
<th scope="col">
<button class="x-sortable-header__b" type="button">
在庫<i class="fa-solid fa-sort" aria-hidden="true"></i>
</button>
</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">りんご</th><td>120円</td><td>8</td></tr>
<tr><th scope="row">みかん</th><td>180円</td><td>3</td></tr>
<tr><th scope="row">ぶどう</th><td>450円</td><td>5</td></tr>
</tbody>
</table>
```
```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-sortable-header,
.x-sortable-header *:not([class*="fa-"]),
.x-sortable-header *:not([class*="fa-"])::before,
.x-sortable-header *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-sortable-header {
width: 270px;
border-collapse: collapse;
background: #fff;
color: #1b2028;
font-size: 12px;
}
.x-sortable-header__c {
margin-bottom: 6px;
color: #5b6472;
font-size: 11px;
text-align: left;
}
.x-sortable-header th,
.x-sortable-header td {
padding: 7px 10px;
border: 1px solid #e3e6ea;
text-align: left;
}
.x-sortable-header td {
text-align: right;
font-variant-numeric: tabular-nums;
}
/* 見出しのセルは余白を持たず、中のボタンがセルいっぱいに広がる(押せる範囲を広く取る) */
.x-sortable-header thead th {
padding: 0;
background: #f1f3f6;
}
.x-sortable-header__b {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
width: 100%;
min-height: 34px;
padding: 0 10px;
border: 0;
background: none;
color: #1b2028;
font: inherit;
font-size: 11.5px;
font-weight: 700;
cursor: pointer;
}
.x-sortable-header__b .fa-solid {
width: auto;
color: #5b6472;
}
.x-sortable-header__b:hover {
background: #e3e8ef;
}
.x-sortable-header__b:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
/* 並べ替え中の列。向きは矢印の形で示し、色と下線は補助にする */
.x-sortable-header__on {
box-shadow: inset 0 -2px 0 #1d4ed8;
}
.x-sortable-header__on .x-sortable-header__b .fa-solid {
color: #1d4ed8;
}
```
## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。
使いどころ
- 価格・日付・件数のように、利用者ごとに見たい順番が違う表
- 行数が多く、上から読むより並べ替えて探したほうが速いとき
注意点
- 押せる見出しと押せない見出しを見た目で区別する。全部の列に並べ替えを付ける必要はない
- 数値は数として比べる。文字列のまま比べると「1,000円」が「200円」より前に来る
- 漢字の名前は、文字コードの順に並べても読みの順にならない。並べ替え用に読み(ふりがな)を別に持つ
- ページ送りのある表は、全件を並べ替えてから切り分ける。いま見えている1ページだけを並べ替えない
- 押すたびに「昇順 → 降順」を繰り返すのか、「元の順」に戻す段階を挟むのかを決め、サイト内の表で揃える
アクセシビリティの要点
- 見出しのセル th の中に button を置く。th そのものにクリックを付けると、キーボードで届かず、押せることも伝わらない
- 並べ替えている列の th に aria-sort="ascending" か "descending" を付ける。付けるのは同時に1列だけで、他の列からは外す
- 向きは三角や矢印の形で示し、色だけに頼らない。アイコンは aria-hidden="true" にして、向きは aria-sort で伝える
- aria-sort の変化を読み上げない環境があるので、並べ替えたあとに「価格の安い順に並べ替えました」を aria-live="polite" で伝えると確実
- 並べ替えたあとも、フォーカスは押した見出しのボタンに残す