メインコンテンツへスキップ
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
はじめにWebReactSwiftUIJetpack ComposeFlutterReact Nativeプリセット自分のデザインシステムを作るAI エージェントと Tanrei を使うブランドデザイントークンデザインシステムとカスタマイズTheme Studioカラータイポグラフィアイコン形状エレベーションモーションインタラクション状態振る舞いレイアウトアクセシビリティ言葉カラートークンタイプスケールトークン形状トークンエレベーショントークンモーショントークン状態トークンスペースと寸法のトークンコンポーネントボタンメインアクションボタンセグメントボタンボタングループメニュー付きボタンツールバーコピーボタンチェックボックスラジオボタンスイッチチップメニュースライダースライダーフィールドカレンダーDateTimePickerフィールドグループラジオグループチェックボックスグループセレクトセレクトフィールドメニューバーファイルアップロードファイルピッカー 上級者向けドロップゾーンテキスト入力欄 上級者向けテキストフィールドチップフィールドフィールドコンボボックス 上級者向けコンボボックスフィールド数値入力欄 上級者向け数値フィールドカラー入力欄 上級者向けカラーフィールドワンタイムコード入力欄 上級者向けワンタイムコードフィールド埋め込みテキスト入力欄 上級者向けチャットコンポーザーカード区切り線モーダルレイヤー 上級者向けダイアログリストリスト行ボトムシートサイドシートカルーセルコードブロックテーブルポップオーバーステップアコーディオン説明リスト図アプリシェルページヘッダーツリービュータイムライン画像プレビューハンドルサイズ変更できる枠スクロールエリアバッジプログレスインジケーターメーターナンバーティッカーローディングインジケータースナックバーツールチップキーボードショートカットタグインラインコードコールアウトアバター空の状態タググループスケルトンファイル項目色見本マーキーテキストタブタブビューアプリバーナビゲーションバーナビゲーションドロワー検索リンクページネーション前後のリンクステッパーパンくずリスト目次リンクカードFAB メニューステータスTanrei の作り方コントリビューションライセンスと帰属
ホームコンポーネントテーブル

テーブル

行と列のあるデータを示します。列の見出し、行見出し、キャプション、フッター行を持てます。収まらないときは自身の枠の中でスクロールします。列の見出しは、その列の順に行を並べるよう求めることができ、行は選択できます。テーブル自身は並べ替えません。

content_copyMarkdown で表示
カテゴリ
コンテインメント
トークン
comp.table
レベル
基礎

概要

Web
tanrei-table
schedule
SwiftUI
TanreiTable
radio_button_unchecked
Jetpack Compose
TanreiTable
radio_button_unchecked
Flutter
TanreiTable
radio_button_unchecked
React Native
Table
radio_button_unchecked

生成元 docs/status/status.json, specs/table.spec.json

行見出し、末尾にそろえた数値、フッター行

ステータスとプラットフォーム別のコンポーネント数
ステータス Web SwiftUI Compose Flutter
Planned 2 6 6 6
Alpha 31 30 30 29
Beta 4 1 1 2
合計 37 37 37 37

テーブルは、2 つの次元を持つデータのためのものです。価格表、プランの比較、型と既定値を添えた設定の一覧、複数のプラットフォームにまたがる状況、人が扱うレコードなどに使います。列の見出しは、その列の順に行を並べるよう求めることができます。行のないテーブルは、プロダクトの空の状態を示します。行の下のステータス行には、プロダクトの文とページネーションを置けます。行は、行ごとのチェックボックスで選択できます。1 行だけを選ぶときはラジオボタンを使います。行を待っているテーブルは、ビジーであることを伝えます。テーブル自身は、並べ替え、絞り込み、取得、ページ送りのいずれも行いません。渡された行を、渡された順に描きます。列幅の変更は行いません。

Web では、ネイティブの <table> を <tanrei-table> で囲みます。この要素はシャドウルートを持たず、テーブルの display を変えることもありません。そのため、クローラー、表計算ソフトへのコピーと貼り付け、スクリーンリーダーの表ナビゲーションのいずれにとっても、ふつうの HTML の表のままです。ほかのプラットフォームはまだ実装していません。

使い方

<link rel="stylesheet" href="@tanrei/web/table.css" />

<tanrei-table row-headers>
  <table>
    <caption>地域別の訪問数</caption>
    <thead>
      <tr>
        <th scope="col">地域</th>
        <th scope="col" data-align="end" data-wrap="false">1 月</th>
      </tr>
    </thead>
    <tbody>
      <tr><th scope="row">島しょ部</th><td data-align="end">212</td></tr>
    </tbody>
    <tfoot>
      <tr><th scope="row">すべての地域</th><td data-align="end">6,846</td></tr>
    </tfoot>
  </table>
</tanrei-table>

<script type="module">
  import '@tanrei/web/components/table';
</script>
import { Table } from '@tanrei/react';
import '@tanrei/web/table.css';

<Table density="compact" striped label="テーブルのトークン">
  <table>
    <thead>
      <tr><th scope="col">トークン</th><th scope="col">型</th></tr>
    </thead>
    <tbody>
      {tokens.map((token) => (
        <tr key={token.name}><td data-wrap="false">{token.name}</td><td>{token.type}</td></tr>
      ))}
    </tbody>
  </table>
</Table>

@tanrei/web/table.css は、スクリプトが動く前にテーブル全体を描きます。要素自身も同じスタイルシートをページに加えるので、スクリプトだけを読み込んだページでも、少し遅れてテーブルが描かれます。

書くもの

部分 Web での書き方
列 <thead> の 1 行に置く <th scope="col">。そのテキストが見出しです。
列の揃え 見出しセルの data-align="start"、"center"、"end"。end は比べる数値のためのものです。
折り返さない列 見出しセルの data-wrap="false"。識別子、日付、数値に使います。
行 <tbody> の <tr>。フッター行は <tfoot> の <tr> です。
セル <td>。テキスト、コンポーネント、または何も入れません。
行見出し 行の最初のセルを <th scope="row"> にし、要素に row-headers を付けます。
名前 <caption>。すぐ上の見出しがすでにテーブルの名前になっている場合は、要素の label。
並べ替えられる列 見出しセルの aria-sort="none"、"ascending"、"descending" と、そのテキストを囲む <button type="button">。セルの data-value は、sort イベントが運ぶ名前です。
行がないとき <tbody> に置く、<td colspan> 1 つの <tr data-part="empty">。中に空の状態を入れます。
ステータス行 テーブルの後ろに置く <div data-part="status">。<p role="status"> とページネーションを入れます。段落を書かない場合は、要素の status が段落を書きます。
選択できる行 要素の selection="multiple"(選べる行が多くても 1 行のときは selection="single")と、すべての行の最初に置くセル <td data-part="selection">。
選択できる 1 行 <tr> の data-value と、その選択セルに入れる <tanrei-checkbox label-hidden>(single のときは <tanrei-radio-button label-hidden>)。<tr> の data-selected が、選択されていることを示します。
すべての行のチェックボックス 見出し行の選択セルに入れる <tanrei-checkbox label-hidden>。要素の select-all-label が名前になります。single のときは、このセルは空のままにします。
読み込み中のテーブル 要素の loading。
まだ届いていない行の代わりに置く、プレースホルダーの行 <tr data-placeholder aria-hidden="true">。セルは空にするか、スケルトンのプレースホルダーを入れます。

要素は、列の揃えと折り返しを、見出しセルからその列のすべてのセルに伝えます。スクリプトが動く前からそのとおりに描くには、このページのデモのように、各セルにも data-align と data-wrap を書きます。

並べ替え

見出しを押すと、テーブルが求め、このページが行を並べます

商品
メモ
ケトル 15 3,800 IH とガスに対応
スケール 0 2,950
ハンドグラインダー 8 6,400
フィルターペーパー 120 450 100 枚入り

並べ替えられる列は、行がいまどの順に並んでいるかを見出しセルに書き、そのテキストをプロダクト自身のボタンで囲みます。

<th scope="col" aria-sort="ascending" data-value="name"><button type="button">名前</button></th>
<th scope="col" aria-sort="none" data-value="price" data-align="end"><button type="button">価格</button></th>

スタイルシートは、見出しセル全体をボタンにし、aria-sort に応じた印を描きます。行がその列の順でない間は上下の矢印、昇順の間は上向きの矢印、降順の間は下向きの矢印です。アイコンを書く必要はありません。印はアイコンセットのグリフなので、@tanrei/web/icons.css とアイコンフォントも読み込んでください。

テーブルは並べ替えません。押すと sort を発行するだけで、画面は何も変わりません。行を並べ、新しい aria-sort を書くのはプロダクトです。

table.addEventListener('sort', (event) => {
  const { column, index, direction } = event.detail; // 'price', 2, 'ascending' | 'descending'
  showRows(sortedBy(column, direction));              // プロダクトのデータ、比較、またはサーバー
  for (const th of headerCells) th.setAttribute('aria-sort', th.dataset.value === column ? direction : 'none');
});

direction は、利用者が求めた順です。ascending か、すでにその列の昇順に並んでいるときは descending です。順をなくす 3 回目の押下はありません。求められた順で返す必要はありません。行を別の順に並べても、どの順にも並べなくてもかまいません。

サーバーが並べるテーブルでは、ボタンの代わりにリンクを置きます。<a href="?sort=price&order=asc">価格</a> のように、同じテーブルの逆の順を指すリンクです。スクリプトがなくても動きます。この場合、sort はブラウザーがリンクをたどる前に発行され、event.preventDefault() でページにとどまれます。

行がないとき

行がないとき:見出し行の下に空の状態

条件に合う商品
名前 在庫 価格
search_offフィルターを外すか、短い言葉で検索してください。

すべての列にまたがるセル 1 つの行を書き、その中に空の状態を置きます。

<tbody>
  <tr data-part="empty">
    <td colspan="3"><tanrei-empty-state headline="条件に合う商品はありません">…</tanrei-empty-state></td>
  </tr>
</tbody>

テーブルは、その上下に余白を取り、枠の幅を与え、列を横にスクロールしている間も見える位置に保ちます。テーブル自身の言葉は加えません。この行がなければ、行のないテーブルは見出し行だけになります。

ステータス行

行の下に、プロダクトの文とページネーション

リリース(新しい順)
バージョン リリース日 変更点
2.4.0 5 月 12 日 テーブルの見出し行を固定できます。
2.3.2 4 月 28 日 2 桁の年で入力した日付の修正。
2.3.1 4 月 20 日 画面の外に開くメニューの修正。
2.3.0 4 月 2 日 サイドシートと、控えめなフォーカスリング。
2.2.1 3 月 18 日 開いたままになるツールチップの修正。
2.2.0 3 月 4 日 右から左のレイアウト。

42 件中 1〜6 件

61224

テーブルの後ろ、要素の中に、ステータス行を書きます。ステータスである段落にプロダクトの文を入れ、その後にページネーションを置きます。

<tanrei-table>
  <table>…</table>
  <div data-part="status">
    <p role="status">132 件中 1〜20 件</p>
    <tanrei-pagination label="商品のページ" variant="compact" size="small" page="1" count="7" previous-label="前のページ" next-label="次のページ" count-text="{count} ページ中 {page} ページ"></tanrei-pagination>
  </div>
</tanrei-table>

文は開始側の端に、ページネーションは終了側の端に置かれます。2 つが横に並んで収まらないときは、ページネーションが文の下に来ます。ステータス行は列と一緒に横にスクロールせず、行が自身の枠の中でスクロールするテーブルでは、枠の下端にとどまります。テーブルは何も数えず、言葉も書きません。「132 件中 1〜20 件」は、プロダクトがプロダクトの言語で書く文です。段落のテキストを変えると、支援技術に控えめに伝わります。マークアップに段落がない場合は、要素の status 属性がこの段落を代わりに書きます。自分で書いた段落があれば、読まれるのはその段落で、属性は何も変えません。

終了側の端に置くのは、サイズ small のページネーションです。ここのようにコンパクトな形にするか、番号でページを選ぶ場合は番号の形にします。1 ページに表示する行数を選べるようにする場合は、そのためのセレクトをページネーションの前に置き、2 つを 1 つの要素にまとめてレイアウトします。表示中の行の範囲はプロダクトの文であり、ページネーションのものではありません。前後のリンクはここには置きません。前後のリンクはタイトルのあるページへ導くもので、1 つのリストの行へ導くものではないからです。テーブルは渡されたものを配置するだけです。ページが変わったら、行と文をプロダクトが変えます。

行の選択

行を選択する: テーブルが選択を保持し、このページが数える

再発注する商品
商品名 在庫 価格
ペーパーフィルター 120 450
ハンドグラインダー 8 6,400
ケトル 15 3,800
スケール 0 2,950

4 件中 2 件を選択

要素に selection="multiple" を指定し、すべての行を選択セルで始めます。見出し行の選択セルには、すべての行のチェックボックスを入れます。本体の行の選択セルにはその行のチェックボックスを入れ、行の data-value に、プロダクトがそのレコードを呼ぶ名前を書きます。

<tanrei-table selection="multiple" row-headers select-all-label="すべての商品を選択">
  <table>
    <caption>再発注する商品</caption>
    <thead>
      <tr>
        <td data-part="selection"><tanrei-checkbox label-hidden></tanrei-checkbox></td>
        <th scope="col">商品名</th>
        <th scope="col" data-align="end">価格</th>
      </tr>
    </thead>
    <tbody>
      <tr data-value="grinder" data-selected>
        <td data-part="selection"><tanrei-checkbox label-hidden checked></tanrei-checkbox></td>
        <th scope="row">ハンドグラインダー</th>
        <td data-align="end">6,400</td>
      </tr>
    </tbody>
  </table>
  <div data-part="status"><p role="status">4 件中 1 件を選択中</p></div>
</tanrei-table>

選択はテーブルが保持します。利用者がチェックボックスを切り替えると、要素はその行に data-selected を書き、見出し行のチェックボックスを合わせ、selectionchange を 1 回発行します。イベントは、選択されているすべての行の値を、行の順に運びます。

table.addEventListener('selectionchange', (event) => {
  if (!(event instanceof CustomEvent)) return;       // 下の注記を参照
  const { values } = event.detail;                   // ['grinder', 'scale']
  // プロダクトの文を、プロダクトの言語で、自分で書いた段落に入れる
  table.querySelector('[role="status"]').textContent = `${total} 件中 ${values.length} 件を選択`;
});
  • プロダクトの側から行を選択するには、その <tr> に data-selected を書きます。選択を解くには、この属性を外します。要素は行のチェックボックスを合わせ、イベントは発行しません。
  • 行が選択されているかどうかは、その行自身(data-selected)に書かれます。別の順で渡し直した行は、選択されたまま、または選択されていないままです。テーブルに初めて渡す行は、その行自身がそう示している場合にだけ選択されます。
  • data-value のない行は選択できません。フッター行と同じように、選択セルを空にします。
  • 見出し行のチェックボックスは、すべての行が選択されている間はチェック、一部が選択されている間は中間状態、選択できる行がない間は無効になります。これが表すのは、テーブルに渡された行です。ほかのページの行は表しません。
  • 選択の列は、データの列ではありません。row-headers と sticky-first-column は、その次の列を指します。sort イベントの index も、選択の列を数えません。
  • 選択できる行は、どこを押しても、チェックボックスを押したときと同じようにそのチェックボックスが変わります。セルの中のリンク、ボタン、入力欄はそれぞれ自分の押下を保ち、ドラッグ、テキストの選択、ダブルクリックの 2 回目のクリック、修飾キーを押しながらのクリックでは何も変わりません。行はひとまとまりでホバーと押下の状態を描きます。キーボードの操作はチェックボックスのままです。

ブラウザーにも、テキストの選択範囲が変わったときの selectionchange という同じ名前のイベントがあり、セルの中のテキストフィールドは、これをテーブル越しに送ります。テーブルのイベントは detail.values を運ぶ CustomEvent で、ブラウザーのイベントはそうではありません。上の例のように、これで見分けます。

スクリプトなしで動く必要があるページでは、ネイティブのチェックボックスを使います。チェックボックスが checked である行は、スタイルシートだけで選択された行として描かれ、チェックボックスはフォームと一緒に送信されます。

<tr data-value="grinder">
  <td data-part="selection"><input type="checkbox" name="product" value="grinder" checked aria-label="ハンドグラインダーを選択" /></td>
  <th scope="row">ハンドグラインダー</th>
</tr>

1 行の選択

1 行を選ぶ:各行にラジオボタン、見出し行にはなし

変更先のプラン
プラン 席数 月額
スターター 3 0.00
チーム 20 48.00
ビジネス 100 180.00

1 件のレコードを選ぶとき(変更先のプラン、配送先の住所、比較の基準にするビルドなど)は、selection="single" を指定します。各本体行の選択セルにはチェックボックスの代わりにラジオボタンを入れ、見出し行の選択セルは空のままにします。すべての行を表すコントロールはないからです。

<tanrei-table selection="single" row-headers>
  <table>
    <caption>変更先のプラン</caption>
    <thead>
      <tr><td data-part="selection"></td><th scope="col">プラン</th><th scope="col" data-align="end">席数</th></tr>
    </thead>
    <tbody>
      <tr data-value="team" data-selected>
        <td data-part="selection"><tanrei-radio-button label-hidden name="plan" checked></tanrei-radio-button></td>
        <th scope="row">チーム</th>
        <td data-align="end">20</td>
      </tr>
    </tbody>
  </table>
</tanrei-table>
  • 行を選ぶと、それまで選ばれていた行の選択は外れます。selectionchange が運ぶ値は 1 つです:{ values: ['team'] }。
  • 選ばれている行や、そのラジオボタンを押しても、何も変わりません。ラジオボタンは、もう一度押しても選択が外れません。どの行も選ばれていない状態に戻せるようにする場合は、そのためのコントロールを自分で置き、行から data-selected を外します。
  • 1 つのテーブルのラジオボタンは同じ名前を共有し、1 つのグループになります。フォーカスの止まる位置は 1 つで、矢印キーで選択が行から行へ移ります。フォームで送信される名前は自分で付けてください。名前のないものには、要素が名前を付けます。
  • 複数の行に data-selected を付けると、最後に付けた行が選ばれ、同時に付けた行のうちでは最初の行が選ばれます。同時に付けたことは、デバッグビルドで報告します。
  • それ以外はチェックボックスと同じです。行のどこを押しても選択でき、data-selected は利用者側で書くもので、ネイティブの <input type="radio" name="…"> はスクリプトなしで動きます。

読み込み中

読み込み中: プレースホルダーの行と、それを伝えるステータス行

今週の注文
顧客 注文日 合計

読み込み中

行を取得している間、loading を指定します。テーブルはビジーとして公開され、その間に渡されたものを描きます。テーブルは何も取得せず、何も待ちません。プロダクトがそう指定している間だけ、読み込み中です。

<tanrei-table loading>
  <table>
    <thead>…</thead>
    <tbody>
      <tr data-placeholder aria-hidden="true">
        <td><span class="tanrei-skeleton-placeholder"></span></td>
        <td data-align="end"><span class="tanrei-skeleton-placeholder" style="inline-size: 3rem; margin-inline-start: auto"></span></td>
      </tr>
    </tbody>
  </table>
  <div data-part="status"><p role="status">読み込み中</p></div>
</tanrei-table>
  • 最初の読み込みでは、プレースホルダーの行 <tr data-placeholder aria-hidden="true"> を渡します。プレースホルダーの行は、テキスト 1 行分以上の高さを持ち、チェックボックスを持たず、支援技術から隠されます。
  • プレースホルダーの行のセルは、空のままにするか、それぞれにスケルトンのプレースホルダー(@tanrei/web/skeleton.css の class="tanrei-skeleton-placeholder")を入れます。プレースホルダーはセルのテキスト 1 行分なので、行の高さは、置き換わる行と同じになります。ここではクラスだけで足ります。ビジーであることは、テーブルがすでに伝えているからです。
  • 別のページや別の順を読み込むときは、テーブルにあった行を残したまま loading を指定します。利用者が読んでいた行は、読めるままです。
  • 何も薄くせず、何も無効にしません。読み込み中も、見出しとチェックボックスは動きます。
  • loading を指定している間、ほかに行がなくても、空の状態の行は描きません。

Markdown

Markdown のレンダラーは、素の <table> を出力します。ビルド時に囲んでください。Tanrei は、実行時にコンテンツを動かしません。rehype なら小さなステップ 1 つです。

import { visit, SKIP } from 'unist-util-visit';

/** 文書中のすべての表を <tanrei-table> で囲みます。 */
export function rehypeTanreiTable() {
  return (tree) =>
    visit(tree, 'element', (node, index, parent) => {
      if (node.tagName !== 'table' || parent?.tagName === 'tanrei-table') return;
      parent.children[index] = { type: 'element', tagName: 'tanrei-table', properties: {}, children: [node] };
      return SKIP;
    });
}

レンダラーがセルに付ける align="right" は end、align="left" は start として読みます。「数値は右に」という指定は、右から左のページでは「数値は末尾に」になります。

仕様

コンパクトと縞模様:見渡すための表

トークン 参照先 型
comp.table.separation sys.behavior.separation number
comp.table.divider.color sys.color.outline-variant color
comp.table.divider.thickness sys.border.hairline dimension
comp.table.row.gap sys.space.space50 dimension
comp.table.stripe.container.color sys.color.surface-container-low color
comp.table.caption.gap sys.space.space100 dimension

枠より広い表:枠の中でスクロールし、最初の列は残ります

地域別の訪問数
地域 1 月 2 月 3 月 4 月 5 月 6 月 7 月
北部 1,204 1,310 1,288 1,402 1,396 1,450 1,512
中部 3,020 2,985 3,110 3,204 3,190 3,275 3,301
南部 870 902 915 899 940 968 1,002
島しょ部 212 220 198 240 251 263 270

レイアウトが高さを決める表:行がスクロールし、見出し行は残ります

リリース
バージョン リリース日 変更点
2.4.0 5 月 12 日 テーブルの見出し行が、スクロール中も表示されたままになりました。
2.3.2 4 月 28 日 年を 2 桁で入力した日付の問題を修正しました。
2.3.1 4 月 20 日 メニューが画面の外に開く問題を修正しました。
2.3.0 4 月 2 日 サイドシートを追加し、フォーカスリングを控えめにしました。
2.2.1 3 月 18 日 ツールチップが開いたままになる問題を修正しました。
2.2.0 3 月 4 日 右から左のレイアウトに対応しました。
2.1.0 2 月 11 日 日付と時刻のピッカーを追加しました。

構成要素

パーツ 説明
1 container The table's box: as wide as its layout gives it, as tall as its rows unless the layout limits it. It has no fill, edge or corner of its own. When the columns or the rows do not fit, it is the box they scroll in, and then the one part of the table that takes focus.
2 caption · 任意 The name of the table, drawn above its first row at the start edge. It does not scroll sideways with the columns.
3 header-row · 任意 The first row: one header cell for each column. It can stay in view while the rows scroll under it.
4 header-cell · 任意 The name of a column, in a label style and a quieter color than the data.
5 sort-button · 任意 The whole header cell of a column that can be sorted, as one button: the column's name and its mark. It carries hover, focus and press, and asks the product for the rows in the column's order.
6 sort-mark · 任意 The glyph beside the name of a column that can be sorted: a two-way arrow while the rows are not in its order, an arrow up while they ascend by it, an arrow down while they descend.
7 row One record: a cell for each column, as tall as its tallest cell. A row that can be chosen is, for a pointer, one target that presses its checkbox or radio button, with its own hover and press (TBL-58, TBL-59).
8 row-header · 任意 The first cell of a row where the first column names its rows: the cell style at its emphasized weight. The first column can stay in view while the others scroll under it.
9 selection-cell · 任意 The cell before the first column of a table whose rows can be chosen: in a body row the row's checkbox, or its radio button where one row at most is chosen; in the header row the checkbox for every row, or nothing where one row at most is chosen; in a footer row nothing.
10 cell One value: text that wraps inside its column, or a component of the product's (a tag, a link, inline code). It may be empty.
11 divider · 任意 The one hairline between two rows, under the header row and above the footer rows, where rows are told apart by a line; also the line along the inner edge of a first column that stays in view, and along an edge of the box beyond which there is more to scroll to.
12 stripe · 任意 A quiet fill on every second body row.
13 selected-row · 任意 The fill of a body row that is chosen, across its whole width, in place of a stripe.
14 placeholder-row · 任意 A body row that stands for a record that has not arrived: as tall as a row of one line, holding what the product puts in its cells (a Skeleton), and no checkbox or radio button.
15 empty · 任意 What stands in the place of the body rows of a table that has none: the product's own content (an Empty state), as wide as the table's box, under the header row.
16 footer-row · 任意 A row after every body row that sums the columns up or notes something about them: the cell style at its emphasized weight.
17 status · 任意 One line under the last row, outside the columns: the product's sentence about what the table shows now (the range of rows shown, how many are chosen) at the start edge, and its pagination at the end edge. It does not scroll sideways with the columns.

プロパティ

プロパティ 型 既定値 説明
caption string "" The name of the table, drawn above it ("Response times by region"). It is also what assistive technology calls the table and its scroll region. Prefer it to label wherever the page does not already name the table in sight.
label string "" The table's accessible name where no caption is drawn, because a heading just above it already says what it is. Not drawn. Ignored where caption is given.
density regular | compact "regular" How close the rows are. Regular (a 46 row in the default system) is for a table a person reads: a few rows, cells that wrap. Compact (a 32 row, smaller text) is for a table a person scans: hundreds of rows, identifiers and values. A design system may add values.
separation auto | divider | gap "auto" How a row is told from the row next to it. divider: one hairline between them. gap: empty space and no line. Auto takes comp.table.separation, the design system's answer (divider in the default system).
striped boolean false Fills every second body row with a quiet color, for a wide table of many similar rows where the eye loses its row on the way across. It can be combined with either separation.
rowHeaders boolean false Says that the first column names its rows (a component, a region, a plan), so its cells are drawn and exposed as row headers. False for a table whose rows are named by no single column.
stickyHeader boolean false Keeps the header row at the top of the table's box while its rows scroll under it. It has an effect only where the layout limits the table's height, so that the rows scroll in the table's own box.
stickyFirstColumn boolean false Keeps the first column at the start edge while the other columns scroll sideways under it, for a wide table whose first column says what each row is. Released by itself where the column would take more than half of the box.
selection none | single | multiple "none" Whether rows can be chosen, and how many. multiple puts a checkbox before the first column of every body row that has a value, and one in the header row that stands for all of them. single puts a radio button there instead and nothing in the header row: one row at most is chosen, and a person moves the choice from row to row but does not take it back (TBL-60). none draws no such column and ignores a row's selected.
selectAllLabel string "" The accessible name of the checkbox in the header row that chooses every row ("Select all rows"). Not drawn; read only while selection is multiple. From the product's words where the instance gives none.
sortedAscendingLabel string "" What a platform that has no sort state of its own says after the name of the column the rows ascend by ("sorted ascending"). Not drawn. A platform that exposes the direction itself (the web) does not read it.
sortedDescendingLabel string "" What a platform that has no sort state of its own says after the name of the column the rows descend by ("sorted descending"). Not drawn. A platform that exposes the direction itself (the web) does not read it.
loading boolean false Says that the product is fetching the rows — the first ones, another page of them, or the same ones in another order. The table is exposed as busy and draws the rows it is given meanwhile: the ones it had, or placeholder rows. It fetches nothing itself.
status string "" The product's sentence about what the table shows now ("1–20 of 132", "3 of 132 selected", "Loading"), drawn under the last row and told politely to assistive technology when it changes. The table counts nothing and writes no word of its own. Empty draws no status text.

スロット

スロット 説明
empty What the table draws in the place of its body rows while it has none and is not loading: the product's Empty state, or its own sentence. The table gives it the box's width and room above and below, and does nothing else to it. A fixture's string is drawn as plain text in its place.
pagination What stands at the end edge of the status line: one element, the product's Pagination — at size small, and compact where the product does not page by number — or a group the product lays out of a Select for the rows per page and that Pagination, as the table paginations of Carbon and MUI are. The range of rows shown ("1–20 of 132") is the status text, not the slot's. It is never Previous and next links, which lead to titled pages and not to the rows of one list. The table places it and does nothing else to it. A fixture's string is drawn as plain text in its place.

イベント

イベント 説明
sort The user pressed the header of a sortable column; carries column (the column's value), index (its place among the columns of data, from 0) and direction (ascending or descending: the one asked for, TBL-34). The table changes nothing: the product orders its rows and sets the column's sort.
selectionChange The user changed which rows are chosen — by a row's checkbox or radio button, by a press on the row, by the checkbox for every row, or by an arrow key on a radio button; carries values: the values of the chosen rows after the change, in row order. While selection is single it holds the one chosen row's value.

アイテム

このコンポーネントが繰り返すアイテムの種類です。フィクスチャはアイテムを順に並べ、各プラットフォームはそれぞれの流儀で種類ごとに名前を付けます。

column

One column of the table: the text of its header cell, how its cells are aligned and wrapped, and whether its header asks for the rows in its order.

プロパティ 型 既定値 説明
header string "" The text of the column's header cell ("Price"). Empty only where the table has no header row at all, and then for every column.
align start | center | end "start" Where the content of the column's cells sits, header and footer cells included. start for text. end for numbers that are compared, so that their digits line up. center for a mark or a single word that is the same kind of answer in every row.
wrap boolean true Whether the text of the column's cells may wrap. False keeps every cell on one line, for identifiers, dates, numbers and short values; the column is then as wide as its widest cell.
sortable boolean false Says that the rows can be put in this column's order: its header cell is a button that asks for it. The table never orders rows itself. It needs a header: a sortable column without one is drawn as a plain column.
sort none | ascending | descending "none" The order the rows are in by this column, as the product has put them: the header draws it and exposes it. The product sets it after it has ordered its rows; one column at a time is not none.
value string "" The name the product knows the column by ("price"), carried by the sort event. Not drawn. Empty: the event carries the column's index alone.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム th TanreiTableColumn TanreiTableColumn TanreiTableColumn TableColumn
header · プロパティ scope header header header header
align · プロパティ data-align alignment align align align
wrap · プロパティ data-wrap wraps wrap wrap wrap
sortable · プロパティ button isSortable sortable sortable sortable
sort · プロパティ aria-sort sort sort sort sort
value · プロパティ data-value value value value value
備考 A <th scope="col"> in the <thead> row; its text is the header. data-align="end" and data-wrap="false" on that <th> reach every cell of the column: the element writes the positions of such columns on the wrapper (data-columns-end, data-columns-center, data-columns-nowrap) and one rule per position places the cells, so no cell is written to; without script, and from a Markdown renderer, the same attributes (or the renderer's own align attribute, where left and right are read as start and end) sit on each cell. A sortable column: the <th> holds the product's own <button type="button"> (or, for a table ordered by the server, an <a href> to the same table in the other order) around the header's text, and carries aria-sort="none", "ascending" or "descending"; the stylesheet draws the mark from that attribute, so the product writes no glyph. data-value on the <th> is the column's value. An entry of the columns prop.

row · グループ — アイテムを含む

One row of the table: a cell for each column, in the order of the columns; a record that can be chosen where the table's selection says so.

プロパティ 型 既定値 説明
footer boolean false Says that the row is a footer row: drawn after every body row, in the emphasized weight, never striped. For sums, averages and notes about the columns.
value string "" The name the product knows the record by, carried by selectionChange. Not drawn. A body row without a value cannot be chosen: it has no checkbox or radio button.
selected boolean false Whether the row is chosen. Read only while the table's selection is single or multiple and the row has a value; while it is single, one row at most is chosen (TBL-60).
selectLabel string "" The accessible name of the row's checkbox or radio button ("Select Islands"): words about this one record, so the instance's. Not drawn. Empty: it is named by the row's header cell where rowHeaders is true.
placeholder boolean false Says that the row stands for a record that has not arrived. It is drawn as a body row at least one line tall, holds what the product puts in its cells (a Skeleton) or nothing, has no checkbox or radio button and is hidden from assistive technology.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム tr TanreiTableRow row TanreiTableRow Table.Row
footer · プロパティ tfoot isFooter footer footer footer
value · プロパティ data-value value value value value
selected · プロパティ data-selected isSelected selected selected selected
selectLabel · プロパティ aria-label selectLabel selectLabel selectLabel selectLabel
placeholder · プロパティ data-placeholder isPlaceholder placeholder placeholder placeholder
備考 A <tr>; a footer row is a <tr> inside <tfoot>. A row that can be chosen has data-value and a first cell <td data-part="selection"> holding the product's checkbox — <tanrei-checkbox label-hidden label="…">, or a native <input type="checkbox" aria-label="…"> that also works in a form without script; selectLabel is that checkbox's own name. data-selected on the <tr> draws the row as chosen; the element keeps it in step with the checkbox, and the stylesheet also reads a native checkbox that is checked, so a row is drawn chosen before and without script. While selection="single" the selection cell holds a radio button instead — <tanrei-radio-button label-hidden name="…" label="…">, or a native <input type="radio" name="…" aria-label="…"> — the radio buttons of one table share one name (the element gives one to those that have none, so that they form one group), and the header row's selection cell is empty. A placeholder row is <tr data-placeholder aria-hidden="true">. A call in the table's scope: row(footer = false) { cell("…") }.

cell · グループ — アイテムを含む

One value of a row: plain text, a component of the product's, or nothing.

スロット 説明
content What the cell holds in place of plain text: a tag, a link, inline code, a button, a picture, any view of the product's. The table places it and styles nothing inside it (TBL-15).
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム td TanreiTableCell cell TanreiTableCell Table.Cell
備考 A <td>, or a <th scope="row"> for a row header; its children are the content. A cell takes a string or a view builder. cell(text) or cell { content } in a row's scope. TanreiTableCell.text("…") or TanreiTableCell(child: …). Its children are a string or elements.

tag

A tag placed in a cell: the Tag component, as it is drawn anywhere else.

プロパティ 型 既定値 説明
label string "" The tag's text.
variant tonal | outlined | filled "tonal" The tag's look.
status default | info | warning | error | success "default" What the tag reports.
size small | medium "medium" The tag's size; small in a table cell, so that it does not make its row taller.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム tanrei-tag TanreiTag TanreiTag TanreiTag Tag
label · プロパティ label title label label label
variant · プロパティ variant variant variant variant variant
status · プロパティ status status status status status
size · プロパティ size size size size size

状態

振る舞い

EARS 記法による要件です。各プラットフォームの実装は、この ID に対応するテストで検証されます。要件の原文は仕様ファイルのものをそのまま掲載しています。

ID 要件
TBL-1 THE table SHALL draw, from top to bottom, its caption where it has one, one header row holding one header cell for each column, its body rows in the order given, and its footer rows after every body row, each row holding one cell for each column in the order of the columns; THE table SHALL be at least as wide as the layout gives it, SHALL draw no fill, no edge, no corner and no elevation of its own around its rows, and SHALL bring no outer space of its own, so that it is spaced by what holds it.
TBL-2 THE table SHALL size every column from its content: a column SHALL never be narrower than the widest thing in it that cannot break — a word, a number, a cell of a column whose wrap is false, a component — with the cell's inline paddings; WHILE the columns at those least widths fit the table's box THE table SHALL share the box's width among them in proportion to their content, so that the table fills its box and a column of long text takes what the short columns leave; THE table SHALL NOT give a column a width in a unit of its own, and a width the product gives a column SHALL replace what the content would give it.
TBL-3 THE table SHALL wrap the text of a cell inside its column, breaking lines by the rules of the text's language, and SHALL never cut it or end it with an ellipsis; a run with no break opportunity (an identifier, an address) SHALL NOT be broken inside and widens its column instead (TBL-2, TBL-17); WHERE a column's wrap is false THE table SHALL keep every cell of that column, its header cell included, on one line.
TBL-4 THE table SHALL pad every cell at the top and bottom by comp.table.cell.padding-block.regular and at the start and end by comp.table.cell.padding-inline.regular while density is regular, and by comp.table.cell.padding-block.compact and comp.table.cell.padding-inline.compact while density is compact, the first and the last cell of a row included; SHALL set a cell's text in comp.table.cell.text.typescale.regular or comp.table.cell.text.typescale.compact by density, in comp.table.cell.text.color, with digits in figures of equal width so that the numbers of a column line up; SHALL make a row as tall as its tallest cell and no taller, with no height of its own; and SHALL align the cells of a row to the top, so that the first lines of a row share a baseline.
TBL-5 THE table SHALL set a column's header cell in comp.table.header.text.typescale.regular or comp.table.header.text.typescale.compact by density, in comp.table.header.text.color, with the paddings of a cell (TBL-4), and SHALL fill the header row with comp.table.header.container.color; the header cells of a row SHALL align to the bottom, so that the last line of each sits next to the data it names. IF every column's header is empty THE table SHALL draw no header row and no line for it, and the table then relies on its row headers (TBL-6).
TBL-6 WHILE rowHeaders is true THE table SHALL treat the first cell of every body row and of every footer row as the header of its row: it SHALL draw that cell in the cell's text style at its emphasized weight — the style's weight plus sys.typescale.emphasis.body — in comp.table.cell.text.color, with a cell's paddings and no fill of its own, and SHALL expose it as a row header (TBL-24); WHILE rowHeaders is false THE table SHALL draw and expose the first column as cells, except that a first cell the product's own content already marks as the header of its row (on the web a th whose scope is row) SHALL be drawn and exposed as one whatever rowHeaders says: rowHeaders adds row headers and takes none away. WHILE selection is multiple or single the first column is the first column of data, after the selection column (TBL-42).
TBL-7 THE table SHALL align the content of every cell of a column, its header cell and its footer cells included, as the column's align names — start: at the start edge of the cell; end: at the end edge, for numbers that are compared digit by digit; center: in the middle, for a mark or a single word that is the same kind of answer in every row — and SHALL align every line of a wrapped cell the same way; a value of align the table does not define SHALL be treated as start.
TBL-8 WHILE the table's separation is divider (TBL-9) THE table SHALL draw one line of comp.table.divider.thickness in comp.table.divider.color across its whole width under the header row, between every two rows that follow each other, and between the last body row and the first footer row, without adding to a row's height or moving its content; it SHALL draw no line above the first row or below the last, and no line between columns.
TBL-9 THE table SHALL tell a row from the row next to it as its separation names: WHILE separation is auto, as comp.table.separation names, which follows sys.behavior.separation unless a design system sets it apart; WHILE separation is divider or gap, as that value names, whatever the tokens say; divider draws the lines of TBL-8 and gap leaves the space of TBL-10. WHEN separation, or the token it follows, changes THE table SHALL draw the other form at once, without animation, keeping its scroll position; WHEN separation names a value the table does not define THE table SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, divider, and state that in its README.
TBL-10 WHILE the table's separation is gap (TBL-9) THE table SHALL leave comp.table.row.gap of empty space under the header row, between every two rows that follow each other, and between the last body row and the first footer row, SHALL draw none of the lines of TBL-8, and SHALL leave no such space above the first row or below the last; the rows SHALL stay exposed as rows (TBL-24).
TBL-11 WHILE striped is true THE table SHALL fill every second body row — the second, the fourth and so on, counted among the body rows as they are shown — with comp.table.stripe.container.color across the row's whole width, paddings included, and SHALL fill neither the header row nor a footer row with it; the fill SHALL change nothing else about the row, and the row separation of TBL-9 SHALL be drawn as without it.
TBL-12 WHERE caption is not empty THE table SHALL draw it above the header row, at the start edge of the table's box, in comp.table.caption.text.typescale and comp.table.caption.text.color, wrapping inside the box's width, comp.table.caption.gap above the first row; the caption SHALL stay where it is while the columns scroll sideways (TBL-17) and SHALL be the table's accessible name (TBL-23).
TBL-13 WHERE a row's footer is true THE table SHALL draw it after every body row, in the order given, as a footer row: its cells in the cell's text style at its emphasized weight — the style's weight plus sys.typescale.emphasis.body — in comp.table.cell.text.color, aligned as their columns are, never striped, and exposed as rows of the table that follow its body; IF a row whose footer is false is given after a footer row THE table SHALL still draw every body row before every footer row and in debug builds report the order.
TBL-14 WHERE a cell has no content THE table SHALL draw nothing in it, keep its place and its paddings so that the cells after it stay in their columns, and expose it as an empty cell; THE table SHALL NOT put a dash, a word or any other mark of its own in it. IF a row holds fewer cells than the table has columns THE table SHALL keep the row's place and its height and leave the places of the missing cells empty at the row's end: a platform that builds its rows SHALL draw them as empty cells, and a platform that draws the product's own rows (the web) SHALL write no cell into them, so that there the row's line and a stripe end at its last cell, and in debug builds SHALL report the row; IF it holds more THE table SHALL draw only as many as it has columns and in debug builds report the row.
TBL-15 WHERE a cell holds a component or any content of the product's that is not plain text — a tag, a link, inline code, a button, a picture — THE table SHALL place it by the column's alignment (TBL-7) on the cell's first line, SHALL add nothing to it and style nothing inside it, and SHALL let pointer, touch and keyboard input reach it as it would outside the table, in the reading order of the cells.
TBL-16 THE table and its cells SHALL NOT take pointer, touch or keyboard input of their own and SHALL NOT change anything they draw under a pointer, a press or focus, and a row SHALL NOT either except as TBL-58 and TBL-59 say: no row has a focused state, is a stop in the focus order or takes a key; a row that cannot be chosen — every row of a table whose selection is none, the header row, a footer row, a placeholder row and a body row without a value — has no hovered or pressed state and is not pressed as a whole; and a cell is not a stop in the focus order. Four parts of the table take input, each by its own sentences: the header cell of a sortable column, which is a button (TBL-32 to TBL-41); the checkboxes of a table whose selection is multiple, which are the Checkbox component, and the radio buttons of a table whose selection is single, which are the Radio button component (TBL-42 to TBL-47, TBL-60, TBL-61); a body row that can be chosen, which a pointer presses as its checkbox or radio button (TBL-58, TBL-59); and the scroll region while the table scrolls (TBL-22). WHILE no column is sortable and selection is none THE table SHALL emit no event.
TBL-17 WHEN the columns at their least widths (TBL-2) are wider than the table's box THE table SHALL keep its box at the width the layout gives it and scroll its columns sideways inside that box, header row, body rows and footer rows together, beginning at the start edge; it SHALL never be wider than its layout and SHALL never make what holds it scroll sideways; a wheel, a trackpad, a drag on a touch screen and the keys of TBL-22 SHALL scroll it.
TBL-18 WHERE the layout gives the table less height than its rows need THE table SHALL scroll its rows up and down inside its own box, the caption above them as the first thing in the box, where it MAY scroll away with the rows on a platform whose caption is part of what scrolls (the web's native caption), and the status line staying at the bottom of the box (TBL-51); WHERE the layout sets no such limit THE table SHALL be exactly as tall as its caption and its rows, and SHALL set no height and no number of rows of its own.
TBL-19 WHILE stickyHeader is true and the table scrolls its rows in its own box (TBL-18) THE table SHALL keep the header row at the top of that box while the rows pass under it, filled with comp.table.sticky.container.color under comp.table.header.container.color so that nothing shows through it, and SHALL draw a line of comp.table.divider.thickness in comp.table.divider.color under it whatever the separation is while a row is under it; WHILE stickyHeader is false, or the table does not scroll its rows in its own box, THE header row SHALL scroll with the rows.
TBL-20 WHILE stickyFirstColumn is true and the table scrolls sideways (TBL-17) THE table SHALL keep the first column — its header cell, its cells and its footer cells — at the start edge of the box while the other columns pass under it, each of its cells filled with comp.table.sticky.container.color under the fill its row gives it (comp.table.header.container.color in the header row, comp.table.stripe.container.color in a striped row), and SHALL draw a line of comp.table.divider.thickness in comp.table.divider.color along its inner edge for the table's whole height while a column is under it; WHILE both stickyHeader and stickyFirstColumn hold THE first header cell SHALL stay above both. IF the first column is wider than half of the table's box THE table SHALL let it scroll with the other columns, so that a narrow window is never filled by the column that stays.
TBL-21 WHILE the table scrolls sideways and columns lie beyond an inline edge of its box THE table SHALL draw a line of comp.table.divider.thickness in comp.table.divider.color along that edge of the box for the table's whole height, and SHALL remove it when the scroll reaches that edge; at the start edge of a table whose first column is kept in view the line of TBL-20 is that line. THE table SHALL show that there is more with these lines alone: no shadow, no fade and no color that is not the divider's.
TBL-22 WHILE the table scrolls in its own box, sideways or up and down, THE table SHALL make that box one stop in the focus order, before anything in its cells that takes focus, exposed as a region that carries the table's name (TBL-23); WHILE it has keyboard focus the box SHALL draw the system's focus indicator inside its edge — sys.focus.outer-width wide in the color role focus-ring, set in from the edge by sys.focus.offset so that nothing clips it, and never animated — and the arrow keys, Page Up, Page Down, Home and End SHALL scroll it as the platform scrolls a region, the left and right arrow keys moving the view left and right in either layout direction; WHILE the table does not scroll THE table SHALL NOT be a stop in the focus order and SHALL expose no region.
TBL-23 THE table SHALL take its accessible name from caption where caption is not empty, and from label otherwise, and SHALL NOT draw label; IF both are empty THE table SHALL be exposed without a name and in debug builds report that a table has no name, and WHILE such a table scrolls in its own box that box SHALL still be a stop in the focus order (TBL-22), exposed as a region without a name, because the keyboard must be able to scroll it; IF both are given THE caption SHALL be the name and label SHALL be ignored.
TBL-24 THE table SHALL be exposed to assistive technology as a table with its number of rows and columns, its header cells as column headers, its first cells as row headers while rowHeaders is true, and every cell associated with the header of its column and, where there is one, of its row, so that a person moving cell by cell hears what each value belongs to; a platform that has no table semantics SHALL expose each row as one group in reading order and each cell with its column header before its own content. Keeping a header row or a first column in view, striping, the density and scrolling SHALL change nothing that is exposed.
TBL-25 WHEN the table's rows, columns or cells change THE table SHALL draw the new content at once, without a motion, keeping its scroll position where the content it showed still exists, and SHALL NOT announce the change by itself. The one place where the table tells a change is its status line, and what that says is the product's (TBL-52); WHEN rows are replaced because the product ordered or fetched them THE table SHALL keep keyboard focus on the sort button, the checkbox or the radio button that had it, where that control still exists.
TBL-26 THE table SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the table is shown THE table SHALL draw itself for the new direction at once, without a motion of its own, keeping its content and the column a person was looking at in view.
TBL-27 WHERE the layout direction is right-to-left THE table SHALL lay its columns out from the right, the first column at the right edge; SHALL apply paddings and the alignments start and end by the layout direction; SHALL begin its sideways scroll at the right edge; SHALL keep a first column that stays in view (TBL-20) at the right edge with its line on its left; and SHALL draw the lines of TBL-21 at the edges beyond which columns lie. Top and bottom SHALL NOT change: the header row is the first row and the footer rows the last in either direction.
TBL-28 THE table SHALL lay out the text of each cell, of each header cell and of the caption as text of its own direction, isolated from the cells beside it: it takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right name, an identifier, a number with its sign, unit or currency, a version and a date keep their own order in a right-to-left layout; THE table SHALL place that text by the column's alignment in the layout direction, not by the text's own, and SHALL NOT reverse the digits of a number: a column of numbers whose align is end is at the left edge of its cells in a right-to-left layout, with its digits written left to right.
TBL-29 WHILE the text size is enlarged THE table SHALL grow every text style with it, give up none of its paddings and none of its lines (they MAY grow with the text where the design system's units follow the text size), size its columns again from the larger content (TBL-2), wrap what can wrap and scroll in its own box where the columns no longer fit (TBL-17); it SHALL cut no content, SHALL set no fixed height on a row or a cell, SHALL lose no content when the reader raises line height, paragraph spacing, letter spacing or word spacing, and SHALL need no sideways scrolling of what holds it at a width of 320 points.
TBL-30 WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE table SHALL draw every line of TBL-8, TBL-19, TBL-20 and TBL-21 in the platform's text color and its text in that color, SHALL keep header cells, row headers and footer cells told apart by their text styles, and, because the mode removes fills, SHALL draw the lines of TBL-8 in a striped table whatever its separation is, so that its rows are still told apart.
TBL-31 WHEN density names a value that the active design system does not have THE table SHALL render with the default value's tokens, and in debug builds report the unknown value.
TBL-32 WHERE a column's sortable is true and its header is not empty THE table SHALL draw that column's header cell as one button, the sort button: its container is the whole box of the header cell, paddings included, with square corners; it holds the column's header as its label, in the header's text style (TBL-5), and the sort mark — one glyph of the system's icon set in a box of comp.table.header.icon.size, drawn through the icon helper with sys.icon.family.unselected, sys.icon.fill.unselected, the weight comp.table.header.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max — comp.table.header.gap after the label's box, at the height of the label's last line, so that a header that wraps keeps its mark at the edge of its label rather than after the last word; in a column whose align is end the mark SHALL stand before the label, so that the end of the label stays on the edge its numbers end at. The mark's box SHALL be kept whatever the column's sort is, so that a change of order moves no label and changes no column's width. IF sortable is true and header is empty THE table SHALL draw the column as one that is not sortable and in debug builds report it.
TBL-33 THE table SHALL draw the order the product sets and never make one: WHILE a sortable column's sort is none its sort button is the part sort — the label in comp.table.sort.label.color, the glyph swap_vert in comp.table.sort.icon.color, the container in comp.table.sort.container.color; WHILE its sort is ascending or descending it is the part sorted — the label in comp.table.sorted.label.color, the glyph arrow_upward for ascending and arrow_downward for descending in comp.table.sorted.icon.color, the container in comp.table.sorted.container.color. THE table SHALL NOT reorder, compare, filter or hide a row, SHALL NOT change a column's sort itself, and SHALL draw its rows in the order it is given whatever any sort says. WHEN a column's sort changes THE table SHALL draw the other glyph and colors at once, with no motion but that of a state change (TBL-35). IF more than one column's sort is not none THE table SHALL draw each as it is given and in debug builds report it: the rows have one order. A sort on a column whose sortable is false SHALL be ignored.
TBL-34 WHEN the user activates a sort button by pointer, touch, Enter or Space THE table SHALL emit sort exactly once, carrying the column's value, its index among the columns of data and the direction asked for — ascending WHILE the column's sort is none or descending, descending WHILE it is ascending — and SHALL change nothing it draws but the answer to the press: the mark, the colors and the rows stay as they are until the product sets sort and gives the rows in their new order. The direction is what the person asked; the product MAY set another, or none. THE table SHALL keep keyboard focus on the sort button through the press and through the change of rows that follows it. WHERE the product gave the control a destination in place of an action (on the web an anchor in the header cell, whose address is the same table in the other order) THE table SHALL emit sort once and then let the platform follow the destination unless the product cancelled the event, and Space SHALL NOT activate it, as it activates no link.
TBL-35 WHILE a sort button is hovered, keyboard-focused or pressed THE table SHALL render its container, its label, its mark and its opacity from the component tokens of its part (sort or sorted) in the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state. A change between states SHALL take comp.table.motion.state-duration with comp.table.motion.state-easing and SHALL be immediate where motion is reduced. The state layer SHALL cover the header cell and nothing beside it, and WHILE the header row or the column is kept in view (TBL-19, TBL-20) SHALL be drawn over comp.table.sticky.container.color, so that nothing shows through it.
TBL-36 WHEN a sort button receives keyboard focus THE table SHALL show the system's focus indicator inside the edge of its header cell without animation — sys.focus.outer-width wide in the color role focus-ring, set in from the edge by sys.focus.offset so that neither the next cell, the box's edge nor a header kept in view clips it, with the inner stroke of sys.focus.inner-width where the system has one; focus gained by pointer or touch SHALL NOT show it. WHEN keyboard focus moves to a sort button, a checkbox or a radio button that is under a header row, a first column or a status line kept in view, or outside the box, THE table SHALL scroll its box so that the whole control is in view beside them.
TBL-37 WHEN a sort button is pressed by pointer, touch, Enter or Space THE table SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner.
TBL-38 WHILE a sort button is pressed THE table SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; label, mark and opacity still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.table.motion.feedback-duration with comp.table.motion.state-easing, fades over comp.table.motion.state-duration with comp.table.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
TBL-39 THE table SHALL show the keyboard focus of a sort button as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README. A sort button's container has square corners, the header cell's, so sys.shape.corner-smoothing has nothing to ease on it.
TBL-40 THE table SHALL make a sort button's hit area its whole header cell, with no gap to the hit area of the header cell beside it. WHERE input is touch, or the platform cannot tell the input type, a header row that holds a sort button SHALL be at least sys.target.touch high, drawn so, at both densities, because a header cell cannot extend a hit area over the row under it; for a fine pointer it SHALL be at least sys.target.pointer high WHILE density is regular, and WHILE density is compact as high as its density gives. A header cell narrower than the target SHALL extend its hit area along the row no further than halfway into the paddings between it and the cell beside it.
TBL-41 THE table SHALL expose a sortable column's header cell as the column header it is (TBL-24) with the order its sort names — ascending, descending, or none for a column that can be sorted and is not — and the sort button inside it as a button named by the column's header; the sort mark SHALL be hidden from assistive technology. On the web these are aria-sort on the header cell and the product's own button in it. WHERE a platform has no sort state THE table SHALL say the order after the button's name with sortedAscendingLabel or sortedDescendingLabel, which come from the instance or from the product's words under the keys of the same names and from nowhere else; IF such a platform is given neither, THE table SHALL expose the button by its name alone and in debug builds report the missing word once. THE table SHALL NOT announce an order itself when it changes: the header cell's state says it, and a product that wants it spoken writes it in status (TBL-52).
TBL-42 WHILE selection is multiple or single THE table SHALL draw a selection column before its first column: in the header row a cell holding the checkbox for every row WHILE selection is multiple (TBL-45) and nothing WHILE it is single (TBL-60), in each body row that has a value a cell holding that row's control — a checkbox WHILE selection is multiple, a radio button WHILE it is single — and an empty cell in a body row without a value, in a placeholder row and in a footer row. Each checkbox is the Checkbox component and each radio button the Radio button component, as their own specs draw them, with no label drawn, placed on the cell's first line (TBL-15) with the edge of its box or ring, not of its round container, at the cell's padding, so that the boxes and rings stand on the line the text of the other cells starts at: the selection cell's paddings are a cell's paddings less half of what the control's container exceeds its box by — comp.checkbox.container.size over comp.checkbox.box.size, comp.radio-button.container.size over comp.radio-button.ring.size — (never less than 0), the round container reaching into them, and the column is as wide as the control's container and those paddings. The selection column is not a column of data: it has no header text, the first column of rowHeaders and of stickyFirstColumn is the one after it, and a sort event's index does not count it. WHILE stickyFirstColumn is true the selection column SHALL stay in view with the first column (TBL-20), and the two together are what is released when they would take more than half of the box. WHILE selection is none THE table SHALL draw no such column and no checkbox or radio button, SHALL ignore every row's selected, and SHALL emit no selectionChange.
TBL-43 WHILE selection is multiple or single and a body row's selected is true THE table SHALL draw that row as chosen: comp.table.row.selected.container.color across its whole width, paddings and the selection cell included, in place of the stripe of TBL-11 where the row would have one; the text of its cells and of its row header in comp.table.row.selected.text.color; and its checkbox or radio button checked. What a cell holds that is not plain text SHALL keep its own colors (TBL-15). In a column kept in view the fill SHALL lie over comp.table.sticky.container.color. The lines and the gap between rows (TBL-8, TBL-10) SHALL be drawn as without it. The fill SHALL change at once, without a motion of the table's own. A footer row and a placeholder row are never chosen.
TBL-44 WHEN the user changes a row's checkbox or radio button — by a press, by Space while it has focus, or, for a radio button, by an arrow key (TBL-61) — THE table SHALL set that row's selected to the control's new state, WHILE selection is single let the row that was chosen go (TBL-60), and emit selectionChange exactly once, carrying the values of every chosen row after the change in row order. A press on the row outside its selection cell presses its control where TBL-58 says so, and this sentence holds for that change as for any other. WHEN the product sets a row's selected THE table SHALL draw it and SHALL NOT emit. THE table SHALL keep which rows are chosen by their values: WHEN rows are given again in another order (TBL-33) each row SHALL be drawn chosen or not by its own selected, and the table SHALL carry no selection over to a row it is not given.
TBL-45 WHILE selection is multiple THE table SHALL keep the checkbox for every row in step with the body rows it holds that have a value: checked WHILE each of them is chosen, in its mixed (indeterminate) state WHILE some are and some are not, unchecked WHILE none is, and disabled WHILE there is no such row. WHEN the user activates it while it is unchecked or mixed THE table SHALL choose every such row, and while it is checked SHALL clear every one, and SHALL emit selectionChange exactly once with the values after the change. "Every row" is every row the table has been given: it SHALL NOT claim to choose records the product has not given it (the rows of other pages), and a product that offers that says so with a control of its own. WHILE selection is single there is no such checkbox (TBL-60).
TBL-46 THE table SHALL name each row's checkbox or radio button by that row's selectLabel; WHERE selectLabel is empty and rowHeaders is true the control SHALL be named by the text of the row's header cell; IF it has neither THE table SHALL expose the control without a name and in debug builds report the row once per table. WHILE selection is multiple THE table SHALL name the checkbox for every row by selectAllLabel, from the instance or from the product's words under the key selectAllLabel and from nowhere else; IF neither gives it THE table SHALL still draw the checkbox, expose it without a name and in debug builds report the missing word. A table whose selection is multiple or single SHALL still be exposed as a table (TBL-24), its rows as rows and its selection cells as cells: which rows are chosen is said by the checked state of their checkboxes or radio buttons, the header checkbox says checked, mixed or not checked, and the table SHALL add no selected state to a row and no role of a grid.
TBL-47 THE table SHALL make each sort button and each checkbox its own stop in the focus order, and the radio buttons of a table whose selection is single one stop together (TBL-61), in reading order — the scroll region first while the table scrolls (TBL-22), then the header row's controls from the start, then each row's checkbox, or the one radio button that is the stop, followed by whatever in that row's cells takes focus — and SHALL handle no key that moves focus from cell to cell: the arrow keys scroll the region or do what the focused control does with them, which for a radio button is TBL-61. A checkbox's or radio button's hit area SHALL be its whole selection cell, in width and in height; for a pointer the rest of its row answers too (TBL-58). WHERE input is touch, or the platform cannot tell the input type, a row that holds a checkbox or a radio button SHALL be at least sys.target.touch high, drawn so, at both densities, and its selection cell at least sys.target.touch wide; for a fine pointer the row SHALL be at least sys.target.pointer high WHILE density is regular, and WHILE density is compact as high as its density gives.
TBL-48 WHILE the table has no body row, loading is false and the empty slot has content THE table SHALL draw that content in the place of the body rows: under the header row and its line, before any footer row, as wide as the table's box and staying in view while the columns scroll sideways, with comp.table.empty.padding-block above and below it and a cell's inline padding at its sides (TBL-4), with no line, no stripe and no fill of the table's through it. THE table SHALL expose it as one row holding one cell that spans every column, SHALL add no word, glyph or role of its own to it, and SHALL NOT announce it (TBL-25): where a search or a filter emptied the table, the product's Empty state is the live one. WHILE the table has no body row and the empty slot has no content THE table SHALL draw its caption, its header row and its footer rows and nothing between them. WHILE the table has a body row THE table SHALL NOT draw the empty slot's content.
TBL-49 WHILE loading is true THE table SHALL be exposed as busy; SHALL draw the rows it is given as they are — the rows it had, or placeholder rows (TBL-50) — and SHALL NOT draw the empty slot's content, even with no body row; SHALL keep its header row, its sort buttons, its checkboxes, its radio buttons and its status line working as without it; SHALL change no color, no opacity and no size of its own because of it; and SHALL announce nothing itself when loading begins or ends: the product says it in status (TBL-52). THE table SHALL fetch nothing, wait for nothing and time nothing: loading is true for as long as the product says so.
TBL-50 WHERE a row's placeholder is true THE table SHALL draw it as a body row — counted for stripes, parted from its neighbours as TBL-9 says, with a cell's paddings — at least as tall as a body row holding one line of text at the table's density, however little its cells hold; SHALL place in each cell what the product gives it there (a Skeleton, by TBL-15) or nothing; SHALL draw no checkbox or radio button in its selection cell and count it neither as a body row for the header checkbox (TBL-45) nor as a row for the empty slot (TBL-48); and SHALL hide the row from assistive technology, so that a person hears that the table is busy and not a row of nothing. IF a row's placeholder is true while loading is false THE table SHALL draw it all the same and in debug builds report it.
TBL-51 WHERE status is not empty or the pagination slot has content THE table SHALL draw a status line under its last row: the status text at the start edge in comp.table.status.text.typescale and comp.table.status.text.color, the pagination slot's content at the end edge, both centered on the line's height, with comp.table.status.padding-block above and below, a cell's inline padding at its sides (TBL-4) and at least comp.table.status.gap between the two; WHERE the two do not fit on one line THE table SHALL put the status text above the pagination, comp.table.status.gap apart, each at the start edge, and the text SHALL wrap and never be cut. The status line SHALL be as wide as the table's box and SHALL stay where it is while the columns scroll sideways (TBL-17). WHILE separation is divider THE table SHALL draw one line of comp.table.divider.thickness in comp.table.divider.color above it. WHILE the table scrolls its rows in its own box (TBL-18) the status line SHALL stay at the bottom of that box while the rows pass under it, filled with comp.table.sticky.container.color, with that line above it whatever the separation is while a row is under it. WHERE status is empty and the slot has no content THE table SHALL draw no status line and leave no room for one.
TBL-52 THE table SHALL expose the status text as a status that assistive technology is told of politely WHEN it changes, without moving focus, and SHALL tell it nothing when the text is first drawn with the table. THE table SHALL write no word into it and SHALL count nothing for it: how many rows there are, which of them are shown and how many are chosen are the product's to say, in its own language. THE table SHALL place the content of the pagination slot and SHALL NOT style it, give it keys or listen to it: the table shows the rows it is given, emits nothing about pages, and holds no page number and no page size.
TBL-53 WHERE the layout direction is right-to-left THE table SHALL draw the selection column at the right edge, before the first column; SHALL place a sort mark after its label on the left, and before it — on the right — in a column whose align is end; SHALL draw the status text at the right edge of the status line and the pagination at the left; and SHALL keep the selection column and a first column that stay in view at the right (TBL-27). The glyphs swap_vert, arrow_upward and arrow_downward show an order and not a direction of reading, and SHALL NOT be mirrored. The status text SHALL be laid out as text of its own direction (TBL-28).
TBL-54 WHERE the platform draws with a person's own forced colors THE table SHALL draw a sort button's label and mark in the platform's button-text color and its focus indicator in the platform's highlight color, SHALL keep the sorted column told from the others by its glyph alone, and, because the mode removes the fill of a chosen row, SHALL rely on the row's control — its checkbox, which the Checkbox draws checked in that mode, or its radio button, which the Radio button draws selected there — to say that the row is chosen; it SHALL draw the line above a status line kept in view in the platform's text color (TBL-30).
TBL-55 WHILE the text size is enlarged THE table SHALL grow a sort button's label and its mark's box with it, wrap the label inside its column where the column's wrap allows it and keep the mark on the label's last line, grow the status text and wrap it, keep the selection column as wide as its checkbox or radio button and paddings, and lay the status line out in two lines where one no longer fits (TBL-51); nothing of these SHALL be cut, and TBL-29 holds for a table with every one of them at a width of 320 points.
TBL-56 WHILE the system's icon set cannot yet draw a sort mark (its font is still loading) THE table SHALL draw nothing in the mark's box, never the glyph's name as text, SHALL keep the box, and SHALL draw the glyph once the set can; the sort button SHALL work all the same.
TBL-57 WHERE the platform draws the product's own elements and script does not run (the web) THE table SHALL still draw, from the product's markup alone, everything of TBL-32, TBL-33, TBL-42, TBL-43, TBL-48, TBL-50 and TBL-51 that is drawing: a sort button and the mark of the order the header cell names, the hover, focus and press of the button, the selection column, a chosen row, the empty content, placeholder rows and the status line; a sort button that is an anchor, and checkboxes and radio buttons that are the platform's own inside the product's form, SHALL work as the platform makes them work — radio buttons of one name exclude each other and share one stop without script (TBL-60, TBL-61). What needs script and is then absent: the events, a press on a row outside its checkbox or radio button and the row's hover and press (TBL-58, TBL-59), which are then not drawn, the header checkbox kept in step and choosing every row (TBL-45), the busy state where the product did not write it, keeping focus through a change of rows, and what TBL-22 already loses. Nothing SHALL be hidden that script would have to reveal.
TBL-58 WHILE selection is multiple or single, WHEN a body row that can be chosen (it has a value and a checkbox or radio button, TBL-42) is pressed and released outside its selection cell with the primary mouse button, a touch or a pen, without the pointer moving further than the platform's touch slop between the two, THE table SHALL press that row's control exactly as a press on the control does: a checkbox's row is chosen or let go, a radio button's row is chosen where it was not and stays chosen where it was (TBL-60), and selectionChange is emitted once where the choice changed (TBL-44). For a pointer the whole row is the control's hit area, the way a whole card answers a press while the controls inside it keep their own. THE table SHALL NOT press the control IF the press lands on or inside something in a cell that answers a press itself — a link, a button, a field, a select, a label, a summary, an editable element, an element with a tab index, an element with a widget role, a custom element that takes focus, or an element to which the product gave the pointer cursor where the row's own is not (the pointer the row shows for itself does not count) — which keeps its own behaviour; IF the release ends a text selection, that is, the selection is not empty and lies in the row once the press is released; IF the pointer moved further than the slop (a drag, a scroll); IF a modifier key (Control, Command, Shift, Alt or Option) is held or a button other than the primary is used; IF the press is the second or a later press of a multiple click; IF the product cancelled the click; or IF the press lands inside a table nested in a cell, whose rows are its own. A press in the selection cell is the control's (TBL-47). The header row, whose checkbox stands for every row, a footer row, a placeholder row and a body row without a value SHALL take no such press. The row SHALL get no role, no name, no focus stop, no selected state and no announcement of its own: the checkbox or radio button stays the one control of the row, which the keyboard and assistive technology reach as TBL-46, TBL-47 and TBL-61 say, and a press on the row is a larger target for the same action. WHERE the platform's row answers no press (on the web, without the element's script) the row SHALL show neither the pointer cursor nor a state, and a press there SHALL choose nothing.
TBL-59 WHILE selection is multiple or single and a fine pointer is over a body row that can be chosen where a press would be its control's — its selection cell, or its cells outside anything that answers a press itself (TBL-58) — THE table SHALL draw that row hovered, as one target — the chosen row of a table whose selection is single too, whose press is its radio button's even though it changes nothing, as the radio button itself answers a hover and a press while it is selected — across its whole width, paddings and the selection cell included: its fill in the hovered container color of the row's condition — selected WHILE the row is chosen, unselected otherwise; the default system mixes comp.table.row.selected.state-layer.color or comp.table.row.unselected.state-layer.color into that condition's resting fill — laid over what the row is filled with at rest (its stripe, the chosen fill, comp.table.sticky.container.color in a column kept in view) and short of the gap of TBL-10; the text of its cells in the hovered text color of its condition; and the pointer cursor. WHILE the pointer is over something in a cell that answers a press itself, that thing SHALL keep its own cursor, hover and press, and the row SHALL be drawn at rest. WHEN such a row is pressed by pointer, touch or pen where TBL-58 or TBL-47 makes the press its control's THE table SHALL draw the row pressed without delay and keep it so for at least sys.motion.duration.press-hold, even when the press ends sooner, answering as sys.behavior.pressed-feedback names: overlay draws the pressed container and text colors of the row's condition across the row; dim applies the table's pressed opacity to the whole row while its fill and text stay with the state under the press; none draws no answer; and ripple, which has no one box to grow in across the cells of a row, answers as overlay. The press SHALL end when the pointer leaves the row, moves further than the slop or is cancelled. The fills SHALL change at once, without a motion of the table's own, as the chosen fill does (TBL-43). The checkbox or radio button SHALL keep its own hover, focus and press; a key that presses it or moves to it SHALL NOT draw the row pressed, and its keyboard focus is shown by the control alone. Under forced colors the row's state fills are gone with the other fills (TBL-54).
TBL-60 WHILE selection is single THE table SHALL let one row at most be chosen: each body row that can be chosen holds a radio button in its selection cell (TBL-42), and the header row's selection cell SHALL hold nothing, because no control stands for every row; IF the product puts a control there THE table SHALL NOT draw it and in debug builds report it. The radio buttons of one table SHALL be one group whose options exclude each other (RAD-3): on the web the radio buttons of one name, the name the product gave them or, for those it gave none, one the table gives them. WHEN the user chooses a row that is not chosen — by a press on its radio button or on the row (TBL-58), by Space, or by an arrow key (TBL-61) — THE table SHALL set that row's selected, let the row that was chosen go, and emit selectionChange exactly once, carrying the value of the chosen row alone. WHEN the user activates the radio button of the chosen row, or presses that row, THE table SHALL keep the row chosen and SHALL NOT emit: a radio button is never cleared by its own activation (RAD-2), and a product that lets a person go back to no row chosen offers a control of its own for that, which clears selected. WHEN the product sets selected THE table SHALL draw it and SHALL NOT emit (TBL-44); IF more than one row says it is chosen THE table SHALL draw chosen the one whose selected the product set last — of several set at once, the first in row order — and let the others go without emitting, and in debug builds report several set at once. IF a body row's selection cell holds a checkbox WHILE selection is single, or a radio button WHILE it is multiple, THE table SHALL treat the row as one that cannot be chosen, SHALL NOT draw that control, and in debug builds report it.
TBL-61 WHILE selection is single THE table SHALL expose each row's radio button as a radio, checked or not, named as TBL-46 says, with its position among the table's radio buttons and their number, and SHALL wrap them in no radio group, give a row no selected state and add no role of a grid: a radio group cannot hold the rows of a table, and the table's name and the column the radio buttons stand in say what they choose. THE radio buttons SHALL be one stop in the focus order — the chosen row's, or, while no row is chosen, the first row's that can be chosen — at the place of that row's control in the reading order of TBL-47, and Tab SHALL leave them rather than move between them (RAD-5). WHILE one of them has keyboard focus, Down Arrow and Right Arrow SHALL move focus to the radio button of the next row that can be chosen and Up Arrow and Left Arrow to the previous one's, wrapping from the last to the first, and choose that row as TBL-60 says, with one selectionChange for each key; in a right-to-left layout Left Arrow and Right Arrow SHALL exchange what they do. THE table SHALL scroll its box so that a radio button that receives keyboard focus is in view beside a header row or a status line kept in view (TBL-36), and WHEN rows are given again focus SHALL stay with the radio button of the row of the same value (TBL-25).

プラットフォーム別の名前対応

パリティは API が同一であることを意味しません。同じ契約を、各プラットフォームの流儀で表現します。

仕様 Web SwiftUI Jetpack Compose Flutter React Native
コンポーネント tanrei-table TanreiTable TanreiTable TanreiTable Table
caption · プロパティ caption caption caption caption caption
label · プロパティ label accessibilityLabel contentDescription semanticLabel accessibilityLabel
density · プロパティ density density density density density
separation · プロパティ separation separation separation separation separation
striped · プロパティ striped striped striped striped striped
rowHeaders · プロパティ rowHeaders rowHeaders rowHeaders rowHeaders rowHeaders
stickyHeader · プロパティ stickyHeader stickyHeader stickyHeader stickyHeader stickyHeader
stickyFirstColumn · プロパティ stickyFirstColumn stickyFirstColumn stickyFirstColumn stickyFirstColumn stickyFirstColumn
selection · プロパティ selection selection selection selection selection
selectAllLabel · プロパティ select-all-label selectAllLabel selectAllLabel selectAllLabel selectAllLabel
sortedAscendingLabel · プロパティ sorted-ascending-label sortedAscendingLabel sortedAscendingLabel sortedAscendingLabel sortedAscendingLabel
sortedDescendingLabel · プロパティ sorted-descending-label sortedDescendingLabel sortedDescendingLabel sortedDescendingLabel sortedDescendingLabel
loading · プロパティ loading isLoading loading loading loading
status · プロパティ status status status status status
sort · イベント sort onSort onSort onSort onSort
selectionChange · イベント selectionchange selection onSelectionChange onSelectionChanged onSelectionChange
備考 Form: <tanrei-table> is a custom element with **no shadow root**, wrapped around a native <table> that stays in the light DOM — <tanrei-table><table><caption>…</caption><thead>…</thead><tbody>…</tbody><tfoot>…</tfoot></table></tanrei-table>. The native table is never given another display, so its semantics, a crawler's reading, copy and paste as a table and a screen reader's table navigation are the browser's own. The wrapper is the scroll box (display: block; overflow: auto; max-inline-size: 100%; the product limits its height with max-block-size). Everything drawn comes from one stylesheet, @tanrei/web/table.css, whose selectors start at tanrei-table and are wrapped in :where(); it styles the page without script (an element that is not yet defined is still an element), and the element adopts the same sheet into the root it is connected to, so it also works inside another component's shadow root. Props are attributes of the wrapper: density, separation, striped, row-headers, sticky-header, sticky-first-column, label. caption is the native <caption>; the caption attribute is a convenience that writes one. label becomes aria-label on the table. Script adds only what CSS cannot: while the wrapper overflows it sets tabindex="0", role="region" and aria-labelledby (the caption) or aria-label (TBL-22), and removes them when it does not; it sets data-more-start and data-more-end for the lines of TBL-21, writes the positions of the columns whose header names data-align or data-wrap on the wrapper (data-columns-end, -center, -nowrap; one rule per position places the cells and no cell is written to), gives first cells role="rowheader" where row-headers is set on a table whose body cells are <td> (Markdown), and releases a first column wider than half of the box (TBL-20). Without script the table is fully drawn, scrolls in its box, and keeps its sticky parts; it lacks the two edge lines and, in a browser that does not focus scrollers by itself, the focus stop. Sticky parts are position: sticky on the cells (inset-block-start: 0 on thead th, inset-inline-start: 0 on the first cell of each row) with border-collapse: separate and the lines drawn as cell borders, because collapsed borders do not travel with a sticky cell. The caption stays in place sideways with position: sticky; inset-inline-start: 0 and a width of the wrapper (100cqi). font-variant-numeric: tabular-nums on cells (TBL-4); unicode-bidi: plaintext per cell (TBL-28). Markdown: a renderer emits a bare <table>; the product wraps it at build time (a rehype step of a few lines, shown in the README) — Tanrei does not transform content. Inside .tanrei-prose the element is an island by its tag name (PRS-16). A cell that spans (colspan, rowspan) and a second header row keep working as HTML and are drawn with the same cell styles, but no fixture covers them. As built: the wrapper is a grid of one cell, not display: block, so that the two "there is more" lines are as tall as the table; the attributes the element writes are data-more-start, data-more-end, data-more-above, data-first-column-released, data-columns and the three positions above, and data-separation on the table; the region's aria-labelledby or aria-label is copied from the table's own name where the product named it natively. **Sorting, selection, empty, loading and status: proposed form, for the platform's agent to settle.** Everything new is the product's own markup inside the same <table>, drawn by table.css without script (TBL-57). Sorting: the stylesheet makes the control in a th[aria-sort] fill the cell (the cell's paddings move onto the control) and draws the mark as a generated glyph from the attribute; the element listens for the control's activation and emits sort (cancelable where the control is an anchor) with detail: { column, index, direction }; it never writes aria-sort. Selection: selection="multiple" on the wrapper; the header row's first cell is <td data-part="selection"> (a cell and not a header cell: a header without text is announced as an empty one) holding the checkbox for every row, which the element keeps checked, indeterminate or unchecked and disables with no row to choose, and names from select-all-label or the product's words (setWords({ selectAllLabel })); the element listens to change from the row checkboxes, writes data-selected, and emits selectionchange with detail: { values }. Neither checkbox is moved, wrapped or replaced. sorted-ascending-label and sorted-descending-label are accepted and not read: aria-sort is the browser's. Empty: the product writes <tbody><tr data-part="empty"><td colspan="…">…</td></tr></tbody> with its <tanrei-empty-state> inside; the stylesheet keeps the cell's content as wide as the box (100cqi) and in view sideways, as it does the caption, and the element corrects a colspan that is not the number of columns. Loading: the loading attribute; the element writes aria-busy on the table (a server may write it itself). Status: after the <table>, inside the wrapper, <div data-part="status"><p role="status">…</p> <nav class="tanrei-pagination">…</nav></div>; it is sticky at the inline start with the box's width, and at the block end, with the sticky fill; the status attribute is a convenience that writes the paragraph, as caption writes a caption. Coarse pointers get the taller header row and rows by @media (pointer: coarse). Proposed spelling, for the platform's agent to settle. TanreiTable(caption:, columns: [TanreiTableColumn], density:, …) { TanreiTableRow { TanreiTableCell("…"); TanreiTableCell { TanreiTag("Beta", size: .small) } } }. Built on Grid and GridRow (columns sized from content, which is TBL-2) inside a ScrollView whose axes are the ones that overflow; a table of several hundred rows uses a LazyVStack of rows with column widths measured once from the header and a sample of rows. The platform's own Table is not used: it is a data table of its own look, with selection and sorting, and it collapses to its first column in a compact width. Sticky parts are pinned section headers (pinnedViews) for the header row and an overlaid first column whose offset follows the scroll. Accessibility: SwiftUI has no table container for custom views, so TBL-24's fallback applies — each row is an accessibility container in reading order, each cell's label is its column header followed by its content, header cells carry the header trait, and the scroll view is named by the caption. Sorting, selection, empty, loading and status: TanreiTableColumn(header:, value:, isSortable:, sort:), TanreiTable(selection: $values, onSort: { column, index, direction in … }) where selection is a binding to the set of chosen values, and rows take value:, selectLabel:, isPlaceholder:. A sortable header is a Button whose accessibility value is sortedAscendingLabel or sortedDescendingLabel (SwiftUI has no sort trait); the row's checkbox is TanreiCheckbox. The empty slot and the pagination slot are view builders (empty:, pagination:); the status text is posted as an announcement when it changes; isLoading sets nothing SwiftUI has a trait for, so placeholder rows are hidden and the status says it. Single selection (selection single, TBL-60, TBL-61): the row's control is the platform's Tanrei radio button in place of the checkbox, the header row holds none, and the product's selection holds one value at most. Proposed spelling, for the platform's agent to settle. TanreiTable(columns = listOf(TanreiTableColumn("Price", align = End)), caption = …) { row { cell("…"); cell { TanreiTag("Beta", size = Small) } } }. Compose has no table layout: a custom Layout measures each column's least and preferred widths (intrinsics) and shares the width as TBL-2 says, inside horizontalScroll; a long table is a LazyColumn of rows with the widths measured from the header and the rows composed so far, and stickyHeader for the header row. Semantics: the container sets collectionInfo(rowCount, columnCount) and each cell collectionItemInfo(rowIndex, columnIndex), header cells are heading(), and each cell's content description is preceded by its column header, so TalkBack announces position and header. The scroll container is focusable and named by the caption. Sorting, selection, empty, loading and status: TanreiTableColumn(header, value = "price", sortable = true, sort = TanreiTableSort.Ascending), onSort = { column, index, direction -> }, selection = TanreiTableSelection.Multiple, rows as row(value = "a1", selected = …, selectLabel = …) with onSelectionChange = { values -> }; the product holds both states. A sortable header is Modifier.clickable(role = Role.Button) with stateDescription from sortedAscendingLabel or sortedDescendingLabel; the checkboxes are TanreiCheckbox (ToggleableState.Indeterminate for the header's); empty and pagination are composable slots; the status text is a polite liveRegion. Single selection (selection single, TBL-60, TBL-61): the row's control is the platform's Tanrei radio button in place of the checkbox, the header row holds none, and the product's selection holds one value at most. Proposed spelling, for the platform's agent to settle. TanreiTable(caption: …, columns: [TanreiTableColumn(header: "Price", align: TanreiTableAlign.end)], rows: [TanreiTableRow(cells: [...])]). Built on the framework's Table widget with IntrinsicColumnWidth (flex for a column that wraps), inside a horizontal SingleChildScrollView in a Scrollbar; a long table uses a two-dimensional scroll view with pinned rows and columns for the sticky parts. The Material library's DataTable is not used: it brings its own look, sorting and selection, and reads a Material theme. Semantics: the table, row, cell and column-header roles where the framework version has them, and otherwise one Semantics per cell whose label is the column header followed by the content; the scroll view is a focusable region named by the caption. Sorting and selection: TanreiTableColumn(header:, value:, sortable:, sort:), onSort: (column, index, direction) {}, selection: TanreiTableSelection.multiple, TanreiTableRow(value:, selected:, selectLabel:, placeholder:) and onSelectionChanged: (values) {}; the product holds both states. A sortable header is an InkWell in Semantics(button: true, value: sortedAscendingLabel); the checkboxes are TanreiCheckbox with tristate for the header's; empty and pagination are widgets; the status text is Semantics(liveRegion: true); placeholder rows are in ExcludeSemantics. Single selection (selection single, TBL-60, TBL-61): the row's control is the platform's Tanrei radio button in place of the checkbox, the header row holds none, and the product's selection holds one value at most. Proposed spelling, for the platform's agent to settle. <Table caption="…" columns={[{ header: "Price", align: "end" }]}><Table.Row><Table.Cell>…</Table.Cell></Table.Row></Table>. React Native has no table layout: rows are Views in a horizontal ScrollView (inside a vertical one, or a FlatList with stickyHeaderIndices for a long table), and column widths are measured in a first pass with onLayout and applied in a second, so the first frame of a large table is drawn after measuring. A first column that stays in view is a second column of cells drawn over the scroll view and kept in step with its vertical offset. There is no table role on either native platform: TBL-24's fallback applies — a row is one accessible group and each cell's accessibilityLabel is its column header followed by its content; header cells have accessibilityRole="header". Follows the app's direction where an ancestor's is not known. Sorting and selection: columns { header, value, sortable, sort }, onSort={({ column, index, direction }) => …}, selection="multiple", <Table.Row value selected selectLabel placeholder> and onSelectionChange={(values) => …}; the product holds both states. A sortable header is a Pressable with accessibilityRole="button" and accessibilityValue={{ text: sortedAscendingLabel }}; the checkboxes are Checkbox (accessibilityState={{ checked: "mixed" }} for the header's); empty and pagination are elements; the status text has accessibilityLiveRegion="polite" on Android and is posted with AccessibilityInfo.announceForAccessibility on iOS; accessibilityState={{ busy: true }} while loading. Single selection (selection single, TBL-60, TBL-61): the row's control is the platform's Tanrei radio button in place of the checkbox, the header row holds none, and the product's selection holds one value at most.

トークン

トークン 参照先 解決値(ライト)
--tanrei-comp-table-caption-gap sys.space.space100 8px
--tanrei-comp-table-caption-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-cell-padding-block-compact — 6px
--tanrei-comp-table-cell-padding-block-regular — 12px
--tanrei-comp-table-cell-padding-inline-compact sys.space.space150 12px
--tanrei-comp-table-cell-padding-inline-regular sys.space.space200 16px
--tanrei-comp-table-cell-text-color sys.color.on-surface #1b1b1b
--tanrei-comp-table-divider-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-table-divider-thickness sys.border.hairline 1px
--tanrei-comp-table-empty-padding-block sys.space.space400 32px
--tanrei-comp-table-focused-opacity — 1
--tanrei-comp-table-header-container-color — #000000 · 0%
--tanrei-comp-table-header-gap sys.space.space50 4px
--tanrei-comp-table-header-icon-size sys.icon.extra-small 16px
--tanrei-comp-table-header-icon-weight sys.icon.mark.weight 500
--tanrei-comp-table-header-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-hovered-opacity — 1
--tanrei-comp-table-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-table-motion-state-duration sys.motion.role.state.large 250ms
--tanrei-comp-table-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-table-pressed-opacity — 1
--tanrei-comp-table-row-gap sys.space.space50 4px
--tanrei-comp-table-row-selected-container-color sys.color.secondary-container #262626 · 16%
--tanrei-comp-table-row-selected-hovered-container-color — #262626 · 22.7%
--tanrei-comp-table-row-selected-hovered-text-color — #262626
--tanrei-comp-table-row-selected-pressed-container-color — #262626 · 24.4%
--tanrei-comp-table-row-selected-pressed-text-color — #262626
--tanrei-comp-table-row-selected-state-layer-color comp.table.row.selected.text.color #262626
--tanrei-comp-table-row-selected-text-color sys.color.on-secondary-container #262626
--tanrei-comp-table-row-unselected-container-color — #000000 · 0%
--tanrei-comp-table-row-unselected-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-table-row-unselected-hovered-text-color — #1b1b1b
--tanrei-comp-table-row-unselected-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-table-row-unselected-pressed-text-color — #1b1b1b
--tanrei-comp-table-row-unselected-state-layer-color comp.table.cell.text.color #1b1b1b
--tanrei-comp-table-row-unselected-text-color comp.table.cell.text.color #1b1b1b
--tanrei-comp-table-separation sys.behavior.separation 0
--tanrei-comp-table-sort-container-color — #000000 · 0%
--tanrei-comp-table-sort-focused-container-color — #4b4b4b · 10%
--tanrei-comp-table-sort-focused-icon-color — #4b4b4b
--tanrei-comp-table-sort-focused-label-color — #4b4b4b
--tanrei-comp-table-sort-focused-state-layer-color — #4b4b4b
--tanrei-comp-table-sort-hovered-container-color — #4b4b4b · 8%
--tanrei-comp-table-sort-hovered-icon-color — #4b4b4b
--tanrei-comp-table-sort-hovered-label-color — #4b4b4b
--tanrei-comp-table-sort-hovered-state-layer-color — #4b4b4b
--tanrei-comp-table-sort-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-sort-label-color comp.table.header.text.color #4b4b4b
--tanrei-comp-table-sort-pressed-container-color — #4b4b4b · 10%
--tanrei-comp-table-sort-pressed-icon-color — #4b4b4b
--tanrei-comp-table-sort-pressed-label-color — #4b4b4b
--tanrei-comp-table-sort-pressed-state-layer-color — #4b4b4b
--tanrei-comp-table-sort-state-layer-color comp.table.sort.label.color #4b4b4b
--tanrei-comp-table-sorted-container-color — #000000 · 0%
--tanrei-comp-table-sorted-focused-container-color — #1b1b1b · 10%
--tanrei-comp-table-sorted-focused-icon-color — #1b1b1b
--tanrei-comp-table-sorted-focused-label-color — #1b1b1b
--tanrei-comp-table-sorted-focused-state-layer-color — #1b1b1b
--tanrei-comp-table-sorted-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-table-sorted-hovered-icon-color — #1b1b1b
--tanrei-comp-table-sorted-hovered-label-color — #1b1b1b
--tanrei-comp-table-sorted-hovered-state-layer-color — #1b1b1b
--tanrei-comp-table-sorted-icon-color sys.color.on-surface #1b1b1b
--tanrei-comp-table-sorted-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-table-sorted-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-table-sorted-pressed-icon-color — #1b1b1b
--tanrei-comp-table-sorted-pressed-label-color — #1b1b1b
--tanrei-comp-table-sorted-pressed-state-layer-color — #1b1b1b
--tanrei-comp-table-sorted-state-layer-color comp.table.sorted.label.color #1b1b1b
--tanrei-comp-table-status-gap sys.space.space200 16px
--tanrei-comp-table-status-padding-block sys.space.space100 8px
--tanrei-comp-table-status-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-sticky-container-color sys.color.surface #ffffff
--tanrei-comp-table-stripe-container-color sys.color.surface-container-low #f9f9f9
--tanrei-comp-table-caption-text-typescale sys.typescale.body-small 400 13px/1.5385
--tanrei-comp-table-cell-text-typescale-compact sys.typescale.body-small 400 13px/1.5385
--tanrei-comp-table-cell-text-typescale-regular sys.typescale.body 400 14px/1.5714
--tanrei-comp-table-header-text-typescale-compact sys.typescale.label-small 500 12px/1.3333
--tanrei-comp-table-header-text-typescale-regular sys.typescale.label 500 14px/1.4286
--tanrei-comp-table-status-text-typescale sys.typescale.body-small 400 13px/1.5385

フィクスチャ

基準となる描画パターンです。全プラットフォームが同じ ID でスナップショットを撮り、結果を比較できるようにしています。

生成元 specs/table.spec.json, tokens/comp/table.tokens.json, design/tokens/

ガイドライン

それは表か

テーブルは、行と列のあるデータに使います。ものを横に並べるために使ってはいけません。支援技術はセルごとに行、列、見出しを読み上げるので、表でない内容にとっては雑音になります。並べるにはレイアウトを使います。

2 つ目の次元を持たないキーと値の一覧は、テーブルよりも説明リストに近いものです。

密度

density テキストが 1 行のときの行の高さ 用途
regular(既定) 46 読むための表。数行で、セルが折り返します。
compact 32 見渡すための表。数百行の識別子と値。

行は自身の高さを持ちません。最も高いセルと同じ高さになります。

行の区切り

separation 描くもの
auto(既定) デザインシステムが選んだもの。既定のシステムでは細い線です。
divider 行と行の間、見出し行の下、フッター行の上に、細い線を 1 本。
gap 余白。線は引きません。

striped は、本体の行を 1 行おきに淡い色で塗ります。似た行が多く並ぶ幅の広い表で、目が行を見失いやすいときに使います。どちらの区切りとも組み合わせられます。

列

  • 列の幅は内容から決まります。列は、折り返せない内容より狭くなることはなく、長いテキストの列が、短い列の残した幅を取ります。
  • テキストは折り返し、切り捨てません。識別子は途中で折り返さず、列を広げます。
  • 数値の列は末尾にそろえ、桁を並べます。テキストは先頭のままにします。中央にそろえるのは、どの行でも同じ種類の答えになる印や一語だけです。
  • 列の幅を自分で指定するのは、必要なときだけにします(見出しセルに style="width: …")。指定した幅が、内容から決まる幅に置き換わります。

収まらないとき

  • テーブルは自身の枠の中でスクロールし、ページをスクロールさせません。枠の端に沿った細い線が、その先に列があることを伝えます。 テーブルの周りにスクロールエリアは要りません。端の表示はテーブル自身が描きます。
  • sticky-first-column は、ほかの列がスクロールする間、最初の列を表示したままにします。その列が枠の半分を超える場合は自動で固定を解くので、スマートフォンの画面が固定した列で埋まることはありません。
  • sticky-header は、行がスクロールする間、見出し行を表示したままにします。レイアウトがテーブルの高さを制限している場合(要素の max-block-size)に働きます。ページのスクロールには追従しません。
  • ページ以外のサーフェス(カード、ダイアログ)に置くテーブルで、見出し行や最初の列を固定する場合、またはステータス行がある場合は、--tanrei-comp-table-sticky-container-color にそのサーフェスの色を設定します。これらのパーツはこの色で塗られるので、下を通る行や列が透けて見えません。

並べ替えの指針

  • 利用者がその列で行を探す場合に、列を並べ替えられるようにします。名前、日付、金額、カテゴリーなどです。メモの列は当てはまりません。
  • 印は、並べ替えられるすべての列に、操作していないときも描きます。タッチ画面でも、どの見出しを押せるかが分かります。
  • 並べ替えられている列は、一度に 1 つです。2 つ目の並べ替えキーは、プロダクト自身のコントロールで扱います。
  • 新しい順を読み上げたい場合は、ステータス行に書きます(「価格の高い順に並べ替えました」など)。テーブル自身は何も通知しません。
  • 並べ替えられる列のあるテーブルの見出し行は、標準の密度で 44 以上、タッチではどちらの密度でも 48 の高さになります。

行の選択の指針

  • 削除、書き出し、移動のように、複数のレコードをまとめて扱う場合に、行を選択できるようにします。1 件だけを扱うアクションは、その行の中に置きます。
  • 答えが 1 件のレコードであるとき(プラン、住所、比較の基準にするバージョンなど)は、1 行の選択(single)を使います。レコードを開くためには使いません。
  • 行はどこを押しても選択されます。行からそのレコードも開けるようにするなら、行を押すのではなく、行見出しにリンクを置いてください。
  • 選択している行の数は、ステータス行に書きます(「132 件中 3 件を選択」など)。テーブルは何も数えません。
  • 選択した行に対して働くボタンは、テーブルの上に置きます。ボタンはプロダクトのものです。テーブルは、何が選択されているかを伝えます。
  • 「132 件すべて」は、プロダクト自身のコントロールで提供します。見出し行のチェックボックスが選択するのは、テーブルに渡された行です。ほかのページの行は選択しません。
  • チェックボックスは、それぞれがフォーカスの止まる位置になります。1 ページの行は、Tab キーでたどれる数にとどめ、残りはページに分けます。テーブルのラジオボタンは、まとめて 1 つの止まる位置になります。
  • チェックボックスまたはラジオボタンのある行は、標準の密度で 44 以上、タッチではどちらの密度でも 48 の高さになります。コンパクトな行は、ポインターではコンパクトなままです。
  • タグとインラインコードは、別のバリアントを選ばなければ Tonal です。既定のデザインシステムでは、Tonal の塗りが選択された行の塗りとほぼ同じ色になるため、選択された行の上ではその形が見えなくなります。テキストは読めるままです。行を選択できるテーブルでは、Outlined のタグを使います。

読み込み中の指針

  • 最初の読み込みには、プレースホルダーの行を使います。数は、見込まれる行の数に近づけます。
  • 順やページを変えるときは、行を残します。利用者が読んでいる行を、プレースホルダーの行やローディングインジケーターに置き換えないでください。
  • ステータス行に「読み込み中」と書き、終わったら届いたものを書きます(「132 件中 1〜20 件」など)。
  • loading を外すときは、プレースホルダーの行も取り除きます。残した行はそのまま描かれ、デバッグビルドで報告されます。

行がないときとステータス行

  • 行のないテーブルには空の状態を使い、行がない理由と次にできることを伝えます。検索や絞り込みの結果として出す空の状態は live にして、結果が伝わるようにします。
  • ステータス行には、テーブルがいま示しているものを書きます。行の総数と、そのうち表示している範囲です。行に対して働くボタンは、テーブルの上に置きます。

推奨

  • すべてのテーブルに名前を付けます。誰にでも見えるキャプションか、すぐ上の見出しがすでに内容を伝えている場合は label を使います。
  • 最初の列が各行の内容を示すなら、行見出しとして書きます(<th scope="row"> と row-headers)。
  • 伝えることがないセルは空のままにします。ないこと自体が情報である場合(「利用不可」など)は、それを書きます。

非推奨

  • 画面のレイアウトにテーブルを使わない。
  • 縞模様や色に意味を持たせない。セルの中のステータスは、言葉か、ラベルがそれを伝えるタグで示します。
  • 空のセルを埋めるためにダッシュを入れない。
  • 行を押せるようにしない。見出しに、独自の並べ替えの印や順を書かない。見出しが押せるようになるのは、上のマークアップによるときだけです。行はそのチェックボックスかラジオボタンで選択し、開く行はテーブルの範囲にありません。
  • 読み込み中のテーブルを薄くしたり、無効にしたりしない。

アクセシビリティ

ロール
table
ラベル付け
The table is named by its caption, or by label where no caption is drawn (TBL-23). Header cells name their columns and, while rowHeaders is true, first cells name their rows; every cell is associated with both (TBL-24). While the table scrolls in its own box, that box is a region with the same name, so that a person who reaches it by keyboard hears what scrolls. A sortable column's button is named by the column's header, and the header cell carries the order (TBL-41). A row's checkbox or radio button is named by selectLabel, or by the row's header; the header row's checkbox by selectAllLabel (TBL-46). The radio buttons of a table are not wrapped in a radio group: each is exposed with its position among them (TBL-61). The status line is the product's sentence, told politely when it changes (TBL-52).
最小ターゲット
見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px

キーボード操作

キー 動作
Tab Moves focus to the table's box while the table scrolls in it; then to each sortable column's header button, in reading order; then, row by row, to the row's checkbox where rows can be chosen — where one row at most is chosen, once to the chosen row's radio button, or the first row's while none is — and to whatever in its cells takes focus. A table that does not scroll, sorts nothing and selects nothing is not a stop.
Enter / Space On a sortable column's header: asks for the rows in that column's order — ascending, or descending where they already ascend by it. Where the header is a link, Enter alone.
Space On a row's checkbox: chooses the row or lets it go. On the header row's checkbox: chooses every row, or lets every row go where all are chosen. On a row's radio button: chooses the row where it is not chosen; on the chosen row's, nothing.
Arrow keys On a row's radio button: move to the next row's radio button (Down, and Right in a left-to-right layout) or the previous one's, wrapping, and choose that row. While the box has focus: scroll it. Left and right move the view left and right in either layout direction.
Page Up / Page Down, Home / End While the box has focus: scroll it by a page, or to its beginning or end, as the platform scrolls a region.

備考

  • The table, its cells and every row that cannot be chosen take no input (TBL-16). What does — a sortable column's header, the checkboxes, a row that can be chosen (for a pointer), the scroll box — is a control, a larger target for one, or a region: a header's hit area is its whole cell and a checkbox's its whole selection cell, each at least the touch target high for touch at both densities (TBL-40, TBL-47); the compact density is lower than the pointer target, as a compact tree row is. What is pressable inside a cell (a link, a button) meets the minimum target by its own spec.
  • A table is for data that has rows and columns. Do not use it to place things side by side: assistive technology announces rows, columns and headers for every cell, which is noise around content that is not tabular. Use the layout.
  • Give every table a name (WCAG 1.3.1, 4.1.2): a caption that everyone sees is best; label is for a table whose heading already names it. A table that scrolls and has no name is a focus stop a person cannot tell from any other, so debug builds report it (TBL-23).
  • Header cells are structure, not style (WCAG 1.3.1). Every column has a header cell unless the table has no header row at all; rowHeaders is true whenever the first column says what each row is. A table with two levels of column headers, or cells that span columns or rows, is outside this spec: split it.
  • A scrolling box must be reachable and operable by keyboard (WCAG 2.1.1) and its focus must be visible (2.4.7): TBL-22. It is a stop only while it scrolls, so short tables add nothing to the focus order.
  • Reflow (WCAG 1.4.10) exempts data tables from two-dimensional scrolling, and the table uses that exemption carefully: only its own box scrolls sideways, never the page (TBL-17, TBL-29), and a first column that stays in view is released on a narrow window (TBL-20).
  • Meaning never rests on color: stripes are an aid to the eye and say nothing; a status in a cell is a word, or a tag whose label says it. An empty cell is exposed as empty (TBL-14); where the absence itself is information ("not available", "none"), write it in the cell.
  • Contrast in the default system: cell text is on-surface and header text on-surface-variant, each held to 4.5:1 on every surface and container step by the token invariants, the stripe included. The lines are decorative: rows, headers and the region are exposed without them.
  • Alignment end for numbers is for sighted comparison only; it changes nothing that is exposed. Text is never justified and never centred except in a column the product sets to center.
  • A table with checkboxes stays a table (TBL-46). The grid pattern — one stop for the whole table, arrow keys from cell to cell — is for a sheet whose cells are edited or acted on; it takes over the keys a screen reader uses to read a table, it would replace the roles of the product's own table on the web, and it cannot work without script. The price of staying a table is one Tab stop for each row's checkbox, which pagination keeps to a page of rows.
  • Where a person chooses one record — the plan to change to, the address to ship to, the build to compare against — selection is single, and the selection column holds a radio button, as the single selection of Carbon's data table and of Ant Design's table does: a radio says that the choices exclude each other, and that one is answered once it is (TBL-60). A radio is never cleared by its own activation (HTML, WAI-ARIA APG radio group), so the table offers no way back to none: a product that needs it gives a control of its own. The radio buttons share one name and so behave as one radio group — one Tab stop, the arrow keys move the choice (TBL-61) — which also keeps a page of rows to one stop where checkboxes would be one each. They are not wrapped in an element of the radio group role, which cannot hold the rows of a table; each radio is heard with its row's name, its state and its position ("Select Kettle, radio button, checked, 3 of 4"), inside the table's name. The grid alternative, rows that carry aria-selected (React Aria's table, single selection mode), is declined for the reasons above.
  • A press anywhere on a row that can be chosen presses its checkbox or radio button (TBL-58), as a press on a card or on a list item that can be chosen does: a larger target for a pointer (WCAG 2.5.8), not a second control. The row gets no role, no tab index and no selected state — ARIA allows aria-selected on a row only inside a grid or a treegrid — so that the keyboard and assistive technology meet the one control the row has, its named checkbox or radio button, and lose nothing. What a cell holds that answers a press (a link, a button, a field) keeps it, and a drag that selects text chooses nothing, so the text of a table can still be selected and copied.
  • Sorting is asked for and shown, never done, by the table (TBL-33, TBL-34): the header cell says the order (WCAG 1.3.1, 4.1.2) and the mark repeats it to the eye without relying on color (1.4.1). One column is sorted at a time.
  • A chosen row is said by its checkbox or radio button, checked; the fill is the same fact for the eye and is not relied on (WCAG 1.4.1), which is also what is left in forced colors (TBL-54). In the default system the text of a chosen row is on-secondary-container on a layer of that color over the surface, the pair a selected list item and a selected tree row are drawn in, held to 4.5:1 by the token invariants, and the fill stands 1.1:1 at least from a hovered row; the contrast of what a cell holds beside plain text on that fill is its own component's.
  • Status messages (WCAG 4.1.3): the table announces nothing itself (TBL-25) and gives the product one polite place to say what changed — rows found, rows chosen, loading and loaded (TBL-52). An Empty state in the empty slot that answers a search or a filter is live by its own spec.
  • While loading, the table is busy and placeholder rows are hidden (TBL-49, TBL-50), so that a person does not walk through rows of nothing. Do not take the rows a person was reading away for a spinner where they can stay.
  • Focus is never lost to a change of rows (TBL-25) and a focused control is never left under a part kept in view (TBL-36; WCAG 2.4.11).

生成元 specs/table.spec.json, tokens/sys/measure.tokens.json

コンポーネントが担うこと

  • ネイティブの表は表のままです。行、列見出し、行見出し、セルはブラウザーのものであり、縞模様の有無、密度、スクロール中、列や見出し行の固定中のいずれでも変わりません。
  • キャプションがテーブルの名前になります。キャプションがなければ、label がテーブルの aria-label になり、描画はしません。両方あるときは、キャプションが名前です。
  • テーブルが自身の枠の中でスクロールする間、その枠は、フォーカスが止まる位置の 1 つになります。セルの中のものより前に来て、テーブルの名前を持つリージョンになります。矢印キー、Page Up、Page Down、Home、End でスクロールします。スクロールしないテーブルは、止まる位置を増やしません。
  • フォーカスリングは枠の端の内側に描くので、何にも切り取られません。
  • 行の最初のセルが素の <td>(Markdown の出力)で row-headers を指定している場合、要素がそれらに行見出しのロールを与えます。
  • 右から左のレイアウトでは、列は右から並び、スクロールは右端から始まり、固定した最初の列は右に来ます。各セルのテキストは自身の方向を保ち、数値が逆になることはありません。
  • 文字サイズが 200% のときは、列の幅を決め直し、収まらなければ自身の枠の中でスクロールします。
  • 強制カラーモードでは、すべての線が文字色になります。このモードは塗りを消すので、縞模様のテーブルは、区切りの指定にかかわらず行の線を引きます。
  • 並べ替えられる列の見出しセルは、順(aria-sort)を持つ列見出しです。その中のボタンは、見出しのテキストを名前にします。印は支援技術から隠します。
  • 並べ替えボタンは、フォーカスが止まる位置の 1 つです。Enter と Space で押します。リンクの場合は Enter だけです。押した後も、フォーカスはそのボタンにあります。行と一緒に見出し行も置き換えた場合は、同じ列の新しいボタンにフォーカスが移ります。
  • 並べ替えボタンのフォーカスインジケーターは、見出しセルの内側に描きます。キーボードでフォーカスしたボタンが、固定した最初の列やステータス行の下にある場合は、その外に出るようにスクロールします。
  • 空の状態の行は、セル 1 つの行 1 つです。テーブルは、ロールも言葉も加えません。
  • ステータス行の段落はステータスです。そのテキストが変わると、フォーカスを動かさずに控えめに伝えます。最初に描いたときは何も伝えません。
  • 強制カラーモードでは、並べ替えボタンのラベルと印がボタンの文字色になります。並べ替えられている列は、矢印だけで区別できます。
  • スクリプトがなくても、並べ替えボタン、その印、空の状態の行、ステータス行は同じように描かれ、並べ替えのリンクも動きます。sort イベントにはスクリプトが必要です。
  • 行を選択できるテーブルも、表のままです。グリッドのロールは付かず、行に選択の状態は付かず、セルからセルへフォーカスを動かすキーもありません。行が選択されていることは、その行のチェックボックスまたはラジオボタンのチェックの状態が伝えます。
  • 見出し行のチェックボックスは、チェック、未チェック、中間状態の間は mixed のいずれかを伝え、選択できる行がない間は無効になります。
  • チェックボックスは、それぞれがフォーカスの止まる位置です。見出し行のチェックボックスと並べ替えボタンが先で、その後に各行のチェックボックスが続きます。Space でチェックを入れたり外したりします。
  • 自身の名前を持たない行のチェックボックスやラジオボタンは、その行の見出しセルを名前にします。
  • single のとき、ラジオボタンはまとめて 1 つの止まる位置です。Tab キーは選ばれている行のラジオボタン(どの行も選ばれていなければ最初の行のもの)に止まり、次の Tab キーでテーブルを出ます。矢印キーは次または前の行のラジオボタンへ移ってその行を選び、端では反対側へ回ります。各ラジオボタンは、行の名前、状態、その中での位置とともに読み上げられます(例:「チームを選択、ラジオボタン、チェックあり、3 個中 2 個目」)。ラジオグループではテーブルの行を囲めないため、ラジオグループでは囲みません。
  • 選択セルのどこを押しても、チェックボックスを押したことになります。チェックボックスにフォーカスがある間に行を置き換えたり並べ直したりすると、フォーカスは、同じ data-value の行のチェックボックスに移ります。
  • ポインターにとって、選択できる行はそのチェックボックスの大きな押下範囲です。行にはロールもフォーカスの停止位置も選択状態も付かないので、キーボードとスクリーンリーダーは、これまでどおり名前の付いたチェックボックスを操作します。行のテキストは、引き続き選択してコピーできます。
  • 強制カラーモードでは、選択された行の塗りは消えますが、行が選択されていることはチェックボックスまたはラジオボタンが伝えます。
  • loading を指定している間、テーブルはビジーとして公開されます(aria-busy)。プレースホルダーの行は支援技術から隠すので、利用者に伝わるのは、テーブルがビジーであることであり、中身のない行ではありません。読み込みの開始と終了を、テーブル自身は通知しません。
  • スクリプトがなくても、選択の列、選択された行(data-selected、またはチェックの入ったネイティブのチェックボックス)、プレースホルダーの行は同じように描かれ、ネイティブのチェックボックスはフォームの中で動きます。見出し行のチェックボックスを合わせることと、selectionchange イベントには、スクリプトが必要です。スクリプトがないと、行はホバーもポインターのカーソルも示さず、チェックボックスの横を押しても何も変わりません。
  • 名前のないテーブル、未知の密度、区切り、選択の指定、本体より前に書かれたフッター行、列の数より多い、または少ないセルを持つ行、見出しのない並べ替えられる列、同時に並べ替えられている 2 つの列、名前のない行のチェックボックス、同時に選択済みとされた複数の行、single での種類の違うコントロール、読み込み中でないテーブルのプレースホルダーの行は、デバッグビルドで報告します。

利用者側で行うこと

  • テーブルに名前を付けます。名前のないスクロールするテーブルは、フォーカスが止まっても、ほかの位置と区別がつきません。
  • すべての列に見出しセルを置き、行見出しは行見出しとして書きます。見出しは構造であり、見た目ではありません。
  • 2 段の列見出しや結合セルが必要な表は、できるだけ単純にします。HTML としては動きますが、このコンポーネントはそれらを前提に設計していません。表を分けるほうが、たいてい分かりやすくなります。
  • セルの中の押せるもの(リンク、ボタン)は、自身の名前、フォーカス、ターゲットサイズを保ちます。余白を取ってください。
  • 行を並べたら、見出しセルに新しい aria-sort を書きます。1 つに ascending か descending、ほかの並べ替えられる列に none です。ボタンのない見出しには、この属性を残さないでください。
  • ステータス行の段落は、テキストがない間もページに残します。次に入れるテキストが伝わるようにするためです。
  • 見出し行のチェックボックスには select-all-label で名前を付けます。行見出しのないテーブルでは、各行のチェックボックスにも label で名前を付けます。
  • 読み込みが始まったときと終わったときに、ステータス行にそれを書きます。この変化が伝わる場所は、ここだけです。

言葉

Tanrei は言葉を同梱しません。テーブルが求める言葉は 1 つ、見出し行のチェックボックスの名前です。要素の select-all-label か、setWords() の selectAllLabel で渡します。残りの 2 つは、プロダクトが自分のマークアップに書きます。

言葉 渡す場所
見出し行のチェックボックスの名前(「すべての商品を選択」) 要素の select-all-label。setWords({ selectAllLabel: 'すべての行を選択' }) で、すべてのテーブルに一度だけ渡すこともできます。
行のチェックボックスまたはラジオボタンの名前(「ハンドグラインダーを選択」) その行の <tanrei-checkbox> または <tanrei-radio-button> の label、またはネイティブのものの aria-label。行見出しがあれば省けます。行の見出しがチェックボックスの名前になります。
ステータス行の文(「4 件中 2 件を選択」「読み込み中」) ステータス行の段落。段落を書かない場合は、要素の status。

見出し行のチェックボックスに名前がなくても、チェックボックスは描かれて動きます。ただしアクセシブルな名前がなく、デバッグビルドは、言葉が足りないことを報告します。