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

目次

読んでいるページの見出しを、その見出しへのリンクとして並べ、いま読んでいるセクションの項目に印を付けます。テキストの横に置く場所がないときは、現在のセクションの名前を示す 1 行になり、押すと同じリストが開きます。

content_copyMarkdown で表示
カテゴリ
ナビゲーション
トークン
comp.table-of-contents
レベル
基礎

概要

Web
tanrei-table-of-contents
schedule
SwiftUI
TanreiTableOfContents
radio_button_unchecked
Jetpack Compose
TanreiTableOfContents
radio_button_unchecked
Flutter
TanreiTableOfContents
radio_button_unchecked
React Native
TableOfContents
radio_button_unchecked

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

このページのセクション:スクロールすると、印が付いていきます

目次は、長いページに何があるかと、読み手がいまどこを読んでいるかを示します。それぞれの項目は見出しへのリンクで、下位の見出しは、親の見出しの下に字下げして並べます。現在の項目は、レールの上のマーカーと、太くしたラベルで示します。プロダクトの中でページからページへ移動するためのナビゲーションではありません。それはナビゲーションドロワーです。

Web では、プロダクト側で書いた、アンカーの入れ子のリストを <tanrei-table-of-contents> で囲みます。アンカーは文書の中に残るので、スクリプトが動く前から、ページにはふつうにたどれるリンクのリストがあります。ほかのプラットフォームはまだ実装していません。

使い方

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

<tanrei-table-of-contents label="このページの内容">
  <ul>
    <li><a href="#top">概要</a></li>
    <li>
      <a href="#install">インストール</a>
      <ul>
        <li><a href="#npm">npm</a></li>
      </ul>
    </li>
    <li><a href="#configure">設定</a></li>
  </ul>
</tanrei-table-of-contents>

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

<TableOfContents label="このページの内容" density="compact">
  <ul>
    {headings.map((heading) => (
      <li key={heading.id}>
        <a href={`#${heading.id}`}>{heading.text}</a>
      </li>
    ))}
  </ul>
</TableOfContents>

@tanrei/web/table-of-contents.css は、スクリプトが動く前にタイトルとリストを描きます。スクリプトが読み込まれても、リストは動きません。要素自身も、同じスタイルシートをページに加えます。コンパクトな形のシェブロンはアイコンセットのグリフなので、アイコンフォントが必要です。

書くもの

パーツ Web での書き方
タイトル label 属性。プロダクトの言語で書きます。ナビゲーションのランドマークの名前にもなります。
項目 最初の子が <a href> である <li>。アンカーのテキストが、プレーンテキストとしてラベルになります。
項目の下位の項目 同じ <li> の中で、アンカーの後ろに置く <ul>。
ページの先頭を指す項目 href が #top か # の項目。最初の見出しに届くまでは、この項目が現在の項目です。
見出しにするタイトル slot="title" を付けた子。<h2 slot="title">このページの内容</h2> のように書きます。この場合も、ランドマークの名前は label です。
現在の項目 何も書きません。要素が決めます。tracking="manual" のときは、current に項目の href を設定します。

項目は利用者側で渡すもので、要素が探し出すものではありません。サイトジェネレーターやフレームワークで作るページでは、ビルドの時点で見出しが分かっています。そのため、リストは最初からページの中にあり、クローラーも読めます。

イベント 発火するとき detail
currentchange tracking が auto の間に、現在の項目が変わったとき。 href:新しい項目の href。なければ空文字列
select 項目が実行されたとき。ブラウザーがリンクをたどる前に発火します。キャンセルすれば、ビューを自分で動かせます。 href
toggle コンパクトな形のリストを、利用者が表示したか隠したとき。 open

仕様

密度が compact で 3 階層、3 階層目の項目が現在の項目

コンパクトな形:行を押してください

構成要素

パーツ 説明
1 title · 任意 What the list is, in the product's words ("On this page"). Text above the list, and the label of the compact form's row. It names the navigation landmark. Not a heading unless the product gives one of its own.
2 list The items in the page's order, one column as wide as the layout gives. It has no fill and no edge of its own in the list presentation.
3 container One item's row: the part that shows hover, focus and press. It runs from the rail to the list's end edge at every level of nesting, and has no fill at rest.
4 label An item's text: the heading it leads to, set in further for each level of nesting. It wraps and is never cut.
5 rail One hairline along the list's start edge, as long as the list. The track the marker sits on. Decorative.
6 marker A heavier stroke on the rail beside the current item's row, as high as that row. With the label's heavier weight it is what tells the current item from the others. Decorative: the current state is exposed by the item.
7 disclosure-row · 任意 The compact presentation's row: the title, the name of the current section and a chevron. A button that shows and hides the list below it.
8 supporting-text · 任意 The name of the current section in the disclosure row, on one line, cut where it does not fit. Empty while no item is current.
9 icon · 任意 The disclosure row's chevron (expand_more), turned half a turn while the list is shown. Decorative.
10 focus-ring The indicator shown for keyboard focus, inside the focused row's container.

プロパティ

プロパティ 型 既定値 説明
label string "" The title, in the product's language ("On this page"): drawn above the list and in the compact form's row, and the accessible name of the navigation landmark. Tanrei ships no words of its own. Required; a table of contents without one still renders and is reported (TOC-40).
labelHidden boolean false True draws no title above the list and keeps label as the landmark's name: for a layout whose own heading already says what the list is. The compact form's row always shows the title, which is that row's label.
presentation list | compact "list" How the table of contents stands. list: the title and the whole list, for a column beside the text. compact: one row that shows the title and the name of the current section, and opens the same list below it; for a window with no room beside the text. Which one a window width gets is decided by the layout that places it (an app shell, the product), which also decides where it stands and whether it stays in view.
density regular | compact "regular" How tall a row of the list is, in a table's, an accordion's and a tree view's words; it is not the compact presentation, and applies to the list of either presentation. regular (a 44 row in the default system) holds the pointer target, as every control of the default look does. compact (a 32 row) is for a long list of headings beside the text of a product read with a pointer: it is lower than the pointer target, which is acceptable because it is asked for by name and is never what a product gets by default. For touch a row is never lower than the touch target in either. The label's text style, the indent and the inline padding are the same in both. A design system may add values.
open boolean false In the compact presentation, whether the list is shown below the row. The web sets it itself when the user opens, closes or chooses; a controlled platform leaves it to the caller. Ignored in the list presentation, where the list is always shown.
current string "" The href of the item whose section the person is in; empty while none is. While tracking is auto the table of contents sets it by the rule of TOC-9 and tells the product with currentChange; while tracking is manual it is the product's to set.
tracking auto | manual "auto" Who decides which item is current. auto: the table of contents, from where the items' targets are in the view that scrolls them (TOC-9 to TOC-16). manual: the product, through current — for a page whose sections are not places in one scrolling view (steps shown one at a time, a virtual list), or a product that already knows where its reader is.

スロット

スロット 説明
title The product's own title content in place of the label prop's text above the list: a heading of the level the page's outline needs. Drawn in the title's text style whatever its level; its role and level are kept. label still names the landmark and is still the compact form's row label. No control.

イベント

イベント 説明
currentChange The current item changed while tracking is auto: by the person's scrolling, by an item being chosen, or by the address naming a target. Carries the new item's href, or an empty string when no item is current. Emitted exactly once per change, never for a value the product set, and never while tracking is manual.
select An item was activated by pointer, touch, Enter or assistive technology; carries its href. Emitted before the platform follows the link. A product that moves the view itself (a router, a scroll view the platform's links do not reach) cancels it and moves.
toggle The compact presentation's list was shown or hidden: by the user from the row, by an item being chosen, or by Escape; carries the new open. Emitted exactly once per change, when the change begins.

アイテム

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

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

One heading of the page: a link to it. An item may hold items, the sub-headings of its section, which are drawn one level further in.

プロパティ 型 既定値 説明
href string "" The destination: the heading's place on the page. On the web a fragment of the page's address ("#installation"); on the other platforms the identifier the product registered that section's heading under. An item whose destination is the top of the page (on the web the fragment the platform reads as the top) is the item for what comes before the first heading (TOC-10). Required and unique within the table of contents.
label string "" The heading's text, as plain text: what the item shows and its accessible name. A heading that holds inline code, a tag or a link is given as its text alone. Required.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム li TanreiTableOfContentsItem TanreiTableOfContentsItem TanreiTableOfContentsItem TableOfContentsItem
href · プロパティ href id id id id
label · プロパティ a label label label label
備考 An <li> of the product's list whose first child is its anchor, <a href="#installation">Installation</a>: href is the anchor's own attribute and the label its text. The item's own items are a <ul> after the anchor, inside the same <li>. The element writes aria-current="location" on the current item's anchor and removes it from the others; a product that sets tracking="manual" writes it itself or sets the element's current. TanreiTableOfContentsItem(id:, label:, items: []), a value. id is what the product's headings are registered under. TanreiTableOfContentsItem(id, label, items = emptyList()), a data class. TanreiTableOfContentsItem(id: …, label: …, items: const []), a value. An object of the items prop: { id, label, items? }.

状態

振る舞い

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

ID 要件
TOC-1 THE table of contents SHALL lay out, in the list presentation, its title and below it, comp.table-of-contents.container.gap away, its items in one column in the order given, each item's own items directly after it and before the item that follows it, the whole as wide as the layout it is placed in; it SHALL draw no fill, no edge and no padding of its own around them, SHALL NOT place itself, keep itself in view or limit its own width, and SHALL NOT decide its presentation from the window: where it stands, that it stays while the page scrolls, and which presentation a width gets are the layout's that places it.
TOC-2 WHILE labelHidden is false THE table of contents SHALL draw its title above the list in comp.table-of-contents.title.typescale and comp.table-of-contents.title.color: the label's text, or the content of the title slot where the product filled it, drawn in the same style whatever its level and keeping its role and level. WHILE labelHidden is true THE table of contents SHALL draw no title in the list presentation and SHALL still name its landmark by label (TOC-31).
TOC-3 THE table of contents SHALL draw each item as a row whose container runs from the rail to the list's end edge at every level of nesting, at least as high as its density gives (comp.table-of-contents.row.height.regular for regular and comp.table-of-contents.row.height.compact for compact) and as high as its label needs, with corners comp.table-of-contents.row.shape, holding its label in comp.table-of-contents.label.typescale with the padding of its density above and below it (comp.table-of-contents.row.padding-block.regular for regular and comp.table-of-contents.row.padding-block.compact for compact), comp.table-of-contents.row.padding-inline after it, and before it comp.table-of-contents.row.padding-inline plus comp.table-of-contents.row.indent for each level of nesting; only the label is set in, so that every row's state layer, focus ring and marker start at the same edge and the marker of a nested item sits on the one rail. Rows follow one another with no space between them, so that no place between two rows takes no press. Density changes a row's height and nothing else: the label's text style, where it starts and the rail are the same in both.
TOC-4 THE table of contents SHALL wrap a label that is wider than its row onto further lines that start where its first line starts, breaking where the label's language allows a break and, for a run with no such place that is longer than the line (a long identifier, an address), inside the run; in the list it SHALL NOT cut a label, draw an ellipsis or scroll sideways, and the row SHALL grow by the lines it holds.
TOC-5 THE table of contents SHALL draw the rail along the list's start edge, from the top of the first row to the bottom of the last, comp.table-of-contents.rail.width wide in comp.table-of-contents.rail.color, once for the whole list and not once per level, and SHALL hide it from assistive technology.
TOC-6 THE table of contents SHALL make each item the platform's own link to its href (on the web an anchor element in the document itself, kept there and not copied into a shadow root; on the other platforms an element with the platform's link semantics), exposing the link role, so that whatever the platform gives a link — on the web its context menu, copying its address, opening it in a new tab, a crawler that reads it, and a list of links that works before any script has run — is the platform's; it SHALL draw an item's label without an underline, because an item is a row of a list in a navigation landmark and is not told from text beside it, and SHALL NOT draw a visited state.
TOC-7 WHILE an item is current THE table of contents SHALL draw that item's row from the tokens of the current part in place of the item part's, its label at the weight of comp.table-of-contents.label.typescale plus sys.typescale.emphasis.body, and the marker on the rail beside that row, as high as the row, comp.table-of-contents.marker.width wide in comp.table-of-contents.marker.color, covering the rail there; at most one item SHALL be drawn as current, an item that holds the current item SHALL be drawn as any other item, and no row's lines SHALL break at another place and no row SHALL change its height because an item became or stopped being current, so that the list does not move while the page scrolls. WHEN the current item changes THE table of contents SHALL change the two rows over comp.table-of-contents.motion.state-duration with comp.table-of-contents.motion.state-easing, and at once where motion is reduced; the marker SHALL NOT travel between rows.
TOC-8 THE table of contents SHALL expose the current item as the current location within its set (on the web aria-current with the value location; a platform without that state, the selected state) and every other item without it; WHEN the current item changes THE table of contents SHALL announce nothing, so that scrolling a page is not narrated.
TOC-9 WHILE tracking is auto THE table of contents SHALL take as current the last item, in the page's order, whose target has reached the line, where an item's target is the place its href leads to in the view that scrolls it, the line is where that target comes to rest when a link moves the view to it — the view's start edge, below whatever the product keeps fixed there, plus the space the target keeps before itself (PRS-23) — and a target has reached the line when its start edge is at the line or before it, within one unit of the platform's layout; WHILE no target has reached the line THE table of contents SHALL have no current item. It SHALL NOT take an item as current because its target is merely in view.
TOC-10 WHERE an item's destination is the top of the page (on the web a fragment the platform reads as the top of the document; elsewhere the identifier the product registered for the start of the content) THE table of contents SHALL treat its target as having always reached the line, so that it is current from the start of the page until the first heading reaches the line and the part before the first heading has a name.
TOC-11 WHILE tracking is auto, the view has room to scroll and is at the end of its range THE table of contents SHALL take the last item as current, whether or not its target has reached the line, because a last section shorter than the view never can; WHEN the view leaves the end of its range THE rule of TOC-9 SHALL apply again. A view with no room to scroll SHALL follow TOC-9 and TOC-10 alone.
TOC-12 WHEN the user activates an item by pointer, touch, Enter or assistive technology THE table of contents SHALL emit select exactly once with that item's href and then, unless the product cancelled it, let the platform follow the link as it follows any link (on the web the browser moves the view and the address; the table of contents SHALL NOT scroll the view itself and SHALL NOT hold the navigation back for sys.motion.duration.press-hold); WHILE tracking is auto it SHALL make that item current at once and keep it current while the view moves to the target and after it has arrived — also where the target cannot reach the line — until the view next moves by the person's own scrolling (a wheel, a drag, a key, the scroll bar), from which TOC-9 to TOC-11 apply again. While the view moves to the target THE table of contents SHALL NOT mark the items it passes.
TOC-13 WHEN the table of contents first appears while the page's address names the target of one of its items, or the address comes to name one while it is shown (a link elsewhere on the page, a heading's own anchor, a step back or forward in the history), THE table of contents SHALL, WHILE tracking is auto, make that item current and keep it as TOC-12 keeps a chosen item; it SHALL draw that item as current in its first drawing where the platform knows the address then, and SHALL NOT show another item as current first.
TOC-14 THE table of contents SHALL add no motion of the view of its own: whether the view glides or jumps to a target is the platform's and the product's (on the web the page's own scroll behaviour, which the product turns off where motion is reduced), and TOC-12 holds for either. It SHALL NOT write the address, add an entry to the history or move the view because the person scrolled.
TOC-15 WHILE tracking is manual THE table of contents SHALL draw and expose as current the item whose href equals current, and none WHILE current is empty; it SHALL NOT observe the view, SHALL NOT change current and SHALL NOT emit currentChange, and SHALL still emit select and follow links as TOC-12 says. WHILE tracking is auto and the product sets current THE table of contents SHALL draw that item as current until the rule next gives another.
TOC-16 WHEN the place of a target changes without the person scrolling — content above it loads or is folded away, the window is resized, the text size changes, an item is added or removed — THE table of contents SHALL, WHILE tracking is auto, decide the current item again by TOC-9 to TOC-11, keeping an item that TOC-12 or TOC-13 is holding. IF an item's href leads to no place on the page THE table of contents SHALL still draw it as a link, SHALL never take it as current by the rule, and in debug builds report it.
TOC-17 WHEN the current item changes while its row is not wholly inside the visible part of a scrolling region that holds the list — the table of contents' own in the compact presentation (TOC-27), or the layout's — THE table of contents SHALL scroll that region, and no other, by the least distance that shows the row whole, at once where motion is reduced; it SHALL NOT scroll the view that holds the targets, and SHALL NOT scroll the list WHILE the pointer is over it or keyboard focus is inside it, so that a row is not moved from under a hand.
TOC-18 WHILE an item's row or the disclosure row is hovered, keyboard-focused or pressed THE table of contents SHALL render that row's container, label and opacity from the component tokens of the highest-precedence active state of its part (item, current or disclosure), with precedence pressed over focused over hovered; hover and press anywhere on a row SHALL answer on that row's container; focus gained by pointer or touch SHALL NOT select the focused state.
TOC-19 WHEN a row receives keyboard focus THE table of contents SHALL show the focus ring without animation inside that row's container, following comp.table-of-contents.row.shape, sys.focus.outer-width wide with the inner stroke of sys.focus.inner-width where the system has one, so that the ring is not cut by the neighbouring rows or by the edge of a scrolling region; focus gained by pointer or touch SHALL NOT show it.
TOC-20 WHEN an item's row is pressed by pointer, touch or Enter, or the disclosure row by pointer, touch, Enter or Space, THE table of contents 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. Space on an item does what it does on any link (on the web it scrolls the page) and SHALL NOT show the pressed state.
TOC-21 WHILE pressed THE table of contents 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, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.table-of-contents.motion.feedback-duration with motion.state-easing, fades over motion.state-duration with motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
TOC-22 THE table of contents SHALL ease its rows' container corners and the compact form's corners by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show its keyboard focus 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.
TOC-23 THE table of contents SHALL change a row between its states over comp.table-of-contents.motion.state-duration with comp.table-of-contents.motion.state-easing, and at once where motion is reduced.
TOC-24 THE table of contents SHALL make each row its hit area across the list's whole width, whatever the level of its label, with no gap between the hit areas of two rows; WHERE input is touch, or the platform cannot tell the input type, a row SHALL be at least sys.target.touch high, drawn so, in both densities, because rows that touch cannot extend a hit area over each other; for a fine pointer a row SHALL be at least sys.target.pointer high WHILE density is regular, and WHILE density is compact as high as its density gives (comp.table-of-contents.row.height.compact), the one place where a row of the list is lower than the pointer target: the density is asked for by name and is never the default; the disclosure row SHALL be at least comp.table-of-contents.compact.row.height high and never lower than sys.target.touch for touch or sys.target.pointer for a fine pointer.
TOC-25 WHILE presentation is compact THE table of contents SHALL draw one block as wide as its layout, filled with comp.table-of-contents.compact.container.color with corners comp.table-of-contents.compact.container.shape, holding first the disclosure row: at least comp.table-of-contents.compact.row.height high, comp.table-of-contents.compact.row.padding-inline at its start and end, and in reading order, comp.table-of-contents.compact.row.gap apart, the title in comp.table-of-contents.title.typescale in the disclosure part's label color, the current item's label in comp.table-of-contents.compact.supporting-text.typescale and comp.table-of-contents.compact.supporting-text.color taking the room that is left, on one line and cut with an ellipsis at the end of its own direction where it does not fit, and at the row's end the icon set's expand_more in a box of comp.table-of-contents.compact.icon.size in comp.table-of-contents.compact.icon.color, turned half a turn WHILE open is true. WHILE no item is current the row SHALL show the title and the chevron alone. The title SHALL NOT be cut before the current item's label has been cut to nothing.
TOC-26 WHEN the user activates the disclosure row by pointer, touch, Enter or Space THE table of contents SHALL emit toggle exactly once with the new open and, where it owns open, show or hide the list: WHILE open is true the list of TOC-3 to TOC-7 is drawn below the row inside the block, comp.table-of-contents.compact.list.padding-inline from the block's start and end edges and with comp.table-of-contents.compact.list.padding-block below it, the block growing and what follows it in the layout moving; WHILE open is false the list is not drawn, not exposed to assistive technology and not focusable. The change SHALL take comp.table-of-contents.motion.disclosure-duration with comp.table-of-contents.motion.disclosure-easing, the chevron turning with it, and happen at once where motion is reduced. The table of contents SHALL NOT draw a scrim, SHALL NOT keep focus inside itself and SHALL NOT close because a press landed outside it: whether an open list that the layout keeps over the text closes on such a press is that layout's, through open.
TOC-27 WHILE presentation is compact and open is true THE table of contents SHALL NOT let its block be taller than the room its layout gives it: where the list needs more, the list SHALL scroll inside the block while the disclosure row stays in place, and WHEN the list is shown THE table of contents SHALL bring the current item's row into its visible part without a motion (TOC-17).
TOC-28 WHEN the user activates an item WHILE presentation is compact THE table of contents SHALL hide the list (emitting toggle with false) and follow the link as TOC-12 says, and SHALL NOT move keyboard focus to the disclosure row: the next move of focus continues from the section that was chosen, as it does after any link to a place on the page.
TOC-29 WHEN the user presses Escape WHILE presentation is compact, open is true and keyboard focus is on the disclosure row or inside the list THE table of contents SHALL hide the list, emit toggle with false, and move focus to the disclosure row where it was inside the list. Moving focus out of the table of contents by Tab SHALL NOT hide the list.
TOC-30 WHEN presentation changes while the table of contents is shown THE table of contents SHALL keep its current item, draw the new presentation at once without a motion of its own, set open to false without emitting toggle, and keep keyboard focus on the item it was on where that item is still shown, moving it to the disclosure row where it is not.
TOC-31 THE table of contents SHALL expose itself as a navigation landmark named by label, holding its items as a list in which an item's own items are a list inside that item, each item one link named by its label with its position in its list; the rail, the marker and the chevron SHALL be hidden from assistive technology. WHILE presentation is compact THE table of contents SHALL expose the disclosure row as one button named by label, described by the current item's label, with its expanded or collapsed state, controlling the list. The title SHALL be exposed once: as the landmark's name, and as a heading only where the product gave a heading in the title slot.
TOC-32 IF the table of contents has no items THE table of contents SHALL draw nothing, take no space and expose nothing, in either presentation — no title, no landmark and no disclosure row — so that a page without headings has no empty list. IF it has one item THE table of contents SHALL render as it does for several.
TOC-33 WHILE the text size is enlarged THE table of contents SHALL grow its title, its labels and the disclosure row's texts with it, keep its paddings, comp.table-of-contents.row.indent, comp.table-of-contents.rail.width and comp.table-of-contents.marker.width, wrap its labels as TOC-4 says and let its rows and the disclosure row grow in height; nothing SHALL be cut in the list, and at twice the text size every label SHALL still be readable in a column that held it at the ordinary size.
TOC-34 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 of contents SHALL draw its items' labels in the platform's link color, the current item's label, the title and the disclosure row's texts in the platform's text color, the rail and the marker in the platform's text color with the marker kept comp.table-of-contents.marker.width wide and drawn as a stroke the mode does not remove, the compact form with an edge in the platform's text color in place of its fill, and its focus ring in the platform's highlight color.
TOC-35 WHERE the layout direction is right-to-left THE table of contents SHALL mirror its layout and apply its paddings by logical start and end: the rail and the marker at the right edge, each level's label set in further from the right, lines aligned to the right, and in the disclosure row the title at the right, the current item's label after it and the chevron at the left. The chevron SHALL NOT be mirrored: it turns, and shows no direction along the line.
TOC-36 THE table of contents 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 of contents is shown THE table of contents SHALL draw itself for the new direction at once, without a motion of its own, keeping its current item, its open and its focus.
TOC-37 THE table of contents SHALL lay out its title, each item's label and the current item's label in the disclosure row each as text of its own direction, isolated from the text around 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, a version or a number keeps its own order in a right-to-left layout and reorders nothing beside it; THE table of contents SHALL still place each text and align its lines by the layout direction, so that the labels of one level start at one edge whatever language each is in.
TOC-38 WHILE the system's icon set cannot yet draw the chevron (its font is still loading) THE table of contents SHALL draw nothing in its place, never the glyph's name as text, and SHALL keep its box, so that the disclosure row's texts do not move when the glyph appears.
TOC-39 WHEN presentation or tracking names a value the table of contents does not have THE table of contents SHALL behave as the default value (list, auto), and WHEN density names a value that the active design system does not have it SHALL render with the default density's tokens; in debug builds it SHALL report the unknown value. WHEN density changes while the table of contents is shown THE table of contents SHALL draw the new heights at once, keeping its current item, its open state and its focus. IF current names no item's href WHILE tracking is manual THE table of contents SHALL draw no item as current and in debug builds report it. IF two items have the same href THE table of contents SHALL render both, take the first as the one that can be current, and in debug builds report it. IF items nest more than three levels deep THE table of contents SHALL still render them, setting each level's label in by comp.table-of-contents.row.indent, and in debug builds report the depth.
TOC-40 IF label is empty THE table of contents SHALL still render, drawing no title and, in the compact presentation, a disclosure row that shows the current item's label alone, and in debug builds report that the landmark and the row have no name, unless the product names the landmark (TOC-41). IF an item's label is empty THE table of contents SHALL NOT draw that item or its items, and in debug builds report it.
TOC-41 WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE table of contents SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on its navigation landmark — an id reference by the elements it names in the host's tree or a tree around it, a description after its own. WHILE label is not empty it is the landmark's name, over a name given so; WHILE label is empty, a name given so names the landmark, and the compact disclosure row by that name followed by the current item's label it shows, so that the row's name holds what it draws; neither is then reported as having no name (TOC-40). WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE table of contents SHALL take it off that element.

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

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

仕様 Web SwiftUI Jetpack Compose Flutter React Native
コンポーネント tanrei-table-of-contents TanreiTableOfContents TanreiTableOfContents TanreiTableOfContents TableOfContents
label · プロパティ label label label label label
labelHidden · プロパティ label-hidden labelHidden labelHidden labelHidden labelHidden
presentation · プロパティ presentation presentation presentation presentation presentation
density · プロパティ density density density density density
open · プロパティ open isOpen open open open
current · プロパティ current current current current current
tracking · プロパティ tracking tracking tracking tracking tracking
currentChange · イベント currentchange current onCurrentChange onCurrentChanged onCurrentChange
select · イベント select onSelect onSelect onSelected onSelect
toggle · イベント toggle isOpen onOpenChange onOpenChanged onOpenChange
備考 Form: <tanrei-table-of-contents label="On this page"> around the product's own list in the light DOM — <tanrei-table-of-contents label="On this page"><ul><li><a href="#install">Install</a><ul><li><a href="#npm">npm</a></li></ul></li></ul></tanrei-table-of-contents>. The anchors stay in the document (TOC-6): before the element is defined, and with scripting off, the page holds a plain nested list of links that work, which a crawler, a search index and a static site generator's own heading list all read and write as they are; nothing is copied, and a framework that renders the list keeps owning it. The element has a small shadow root that holds only what the product did not write — a <nav> named by label around the title, the disclosure row (a <button> with aria-expanded and aria-controls) and one <slot> for the list — so the landmark, the title and the compact form need no markup from the product; the title slot takes the product's heading. Everything drawn on the list comes from one stylesheet of the package, @tanrei/web/table-of-contents.css, whose selectors start at tanrei-table-of-contents and are wrapped in :where(): it styles the list before definition too, so the list presentation does not change place when the script loads. Before definition the compact presentation shows its whole list (there is no row yet); a layout that must not show that for a moment hides tanrei-table-of-contents[presentation="compact"]:not(:defined) itself. Level is the nesting of the <ul>s; the label's inset is padding-inline-start computed from a depth custom property the stylesheet sets per nested list. The marker is the anchor's ::before on the rail (a border-inline-start, so forced colors keep it, TOC-34); the heavier weight of the current label must not reflow the list (TOC-7) — reserve the heavier text's width with a hidden copy of the label (::after with content: attr(data-label), written by the element) or an equivalent the three engines agree on. Tracking (TOC-9 to TOC-16): the element resolves each anchor's fragment with document.getElementById, finds the nearest scrolling ancestor of the targets (else the viewport), and reads the line from each target's own computed scroll-margin-block-start plus the view's scroll-padding-block-start, which is where the browser itself rests a target — so prose's heading margin (PRS-23) and the product's fixed header are declared once, in CSS, and never passed to the element. It decides on scroll through one passive listener coalesced to a frame (or an IntersectionObserver per target; no frame is asked for at rest), on resize, and through a ResizeObserver on the scrolled content (TOC-16). TOC-12's hold begins on the anchor's click and on hashchange/popstate, and ends at the first wheel, touchmove, scrolling key or scroll-bar press after scrollend (a timer where scrollend is missing). The element never calls scrollIntoView on a target and never writes location.hash; TOC-17 uses scrollTo on the list's own scroll container only. TOC-28: the element blurs nothing and focuses nothing; it closes the list after the browser has taken the fragment navigation, which sets the sequential focus starting point at the target. select is dispatched from the anchor's click and is cancelable, cancelling the click with it. toggle carries detail.open; currentchange carries detail.href. A list that the product builds from a container's headings is the product's (or its site generator's) to build; the package may export a function that returns the nested items of a container's h2 to h4, and the element does not read headings itself. React: TableOfContents takes items ({ href, label, items? }[]) and renders the same element and list, or children. TanreiTableOfContents(label:, items:, current: Binding<String>, presentation:, isOpen: Binding<Bool>, tracking:, onSelect:). Targets are registered by a modifier on the product's heading views, .tanreiTableOfContentsTarget(id), inside a scroll view marked .tanreiTableOfContentsView(line:), which reports each target's place in the scroll view's coordinate space through a preference; the table of contents applies TOC-9 to TOC-16 to those places and writes current. onSelect is given the id and the product scrolls (a ScrollViewReader proxy or scrollPosition(id:)); TOC-12's hold ends at the next drag of the scroll view. An accessibility container with the navigation role where the platform has one, the items as links (isLink) with isSelected for current; the row is a button whose value is expanded or collapsed. TanreiTableOfContents(label, items, current, onCurrentChange, presentation, open, onOpenChange, tracking, onSelect, state). rememberTanreiTableOfContentsState() holds the targets' places: headings register with Modifier.tanreiTableOfContentsTarget(state, id) (onGloballyPositioned), a lazy list registers the indices of its headings, and the state applies TOC-9 to TOC-16 from the scroll state it is given. onSelect is given the id and the product scrolls (animateScrollToItem, bringIntoView). Items have the link semantics a link annotation has (Compose has no link role for a clickable node) and selected for current; the row has the expanded state with the expand and collapse actions. TanreiTableOfContents(label: …, items: …, current:, onCurrentChanged:, presentation:, open:, onOpenChanged:, tracking:, onSelected:, controller:). A TanreiTableOfContentsController is given the page's ScrollController; headings are wrapped in TanreiTableOfContentsTarget(id: …), whose render object's offset in the scrollable is read after layout, and the controller applies TOC-9 to TOC-16. onSelected is given the id and the product scrolls (Scrollable.ensureVisible). Semantics(link: true, selected: …) for items, expanded for the row; on the web target of Flutter the address is the product's router's. <TableOfContents label="…" items={…} current={…} onCurrentChange={…} />. useTableOfContents() returns what the page's ScrollView and its headings need — an onScroll handler, an onLayout per heading id, and the line — and applies TOC-9 to TOC-16 to them; onSelect is given the id and the product scrolls (scrollTo). accessibilityRole="link" with accessibilityState.selected for items, accessibilityState.expanded for the row. Follows the app's direction where an ancestor's is not known.

トークン

トークン 参照先 解決値(ライト)
--tanrei-comp-table-of-contents-compact-container-color sys.color.surface-container #f6f6f6
--tanrei-comp-table-of-contents-compact-container-shape sys.shape.corner.small 6px
--tanrei-comp-table-of-contents-compact-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-of-contents-compact-icon-size sys.icon.small 20px
--tanrei-comp-table-of-contents-compact-list-padding-block sys.space.space100 8px
--tanrei-comp-table-of-contents-compact-list-padding-inline sys.space.space200 16px
--tanrei-comp-table-of-contents-compact-row-gap sys.space.space100 8px
--tanrei-comp-table-of-contents-compact-row-height — 48px
--tanrei-comp-table-of-contents-compact-row-padding-inline sys.space.space200 16px
--tanrei-comp-table-of-contents-compact-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-of-contents-container-gap sys.space.space100 8px
--tanrei-comp-table-of-contents-current-container-color — #000000 · 0%
--tanrei-comp-table-of-contents-current-focused-container-color — #1b1b1b · 10%
--tanrei-comp-table-of-contents-current-focused-label-color — #1b1b1b
--tanrei-comp-table-of-contents-current-focused-state-layer-color — #1b1b1b
--tanrei-comp-table-of-contents-current-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-table-of-contents-current-hovered-label-color — #1b1b1b
--tanrei-comp-table-of-contents-current-hovered-state-layer-color — #1b1b1b
--tanrei-comp-table-of-contents-current-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-table-of-contents-current-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-table-of-contents-current-pressed-label-color — #1b1b1b
--tanrei-comp-table-of-contents-current-pressed-state-layer-color — #1b1b1b
--tanrei-comp-table-of-contents-current-state-layer-color comp.table-of-contents.current.label.color #1b1b1b
--tanrei-comp-table-of-contents-disclosure-container-color — #000000 · 0%
--tanrei-comp-table-of-contents-disclosure-focused-container-color — #1b1b1b · 10%
--tanrei-comp-table-of-contents-disclosure-focused-label-color — #1b1b1b
--tanrei-comp-table-of-contents-disclosure-focused-state-layer-color — #1b1b1b
--tanrei-comp-table-of-contents-disclosure-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-table-of-contents-disclosure-hovered-label-color — #1b1b1b
--tanrei-comp-table-of-contents-disclosure-hovered-state-layer-color — #1b1b1b
--tanrei-comp-table-of-contents-disclosure-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-table-of-contents-disclosure-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-table-of-contents-disclosure-pressed-label-color — #1b1b1b
--tanrei-comp-table-of-contents-disclosure-pressed-state-layer-color — #1b1b1b
--tanrei-comp-table-of-contents-disclosure-state-layer-color comp.table-of-contents.disclosure.label.color #1b1b1b
--tanrei-comp-table-of-contents-focused-opacity — 1
--tanrei-comp-table-of-contents-hovered-opacity — 1
--tanrei-comp-table-of-contents-item-container-color — #000000 · 0%
--tanrei-comp-table-of-contents-item-focused-container-color — #4b4b4b · 10%
--tanrei-comp-table-of-contents-item-focused-label-color — #4b4b4b
--tanrei-comp-table-of-contents-item-focused-state-layer-color — #4b4b4b
--tanrei-comp-table-of-contents-item-hovered-container-color — #4b4b4b · 8%
--tanrei-comp-table-of-contents-item-hovered-label-color — #4b4b4b
--tanrei-comp-table-of-contents-item-hovered-state-layer-color — #4b4b4b
--tanrei-comp-table-of-contents-item-label-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-of-contents-item-pressed-container-color — #4b4b4b · 10%
--tanrei-comp-table-of-contents-item-pressed-label-color — #4b4b4b
--tanrei-comp-table-of-contents-item-pressed-state-layer-color — #4b4b4b
--tanrei-comp-table-of-contents-item-state-layer-color comp.table-of-contents.item.label.color #4b4b4b
--tanrei-comp-table-of-contents-marker-color sys.color.primary #1b1b1b
--tanrei-comp-table-of-contents-marker-width sys.border.thick 2px
--tanrei-comp-table-of-contents-motion-disclosure-duration sys.motion.role.disclosure 400ms
--tanrei-comp-table-of-contents-motion-disclosure-easing sys.motion.easing.spatial cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-table-of-contents-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-table-of-contents-motion-state-duration sys.motion.role.state.large 250ms
--tanrei-comp-table-of-contents-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-table-of-contents-pressed-opacity — 1
--tanrei-comp-table-of-contents-rail-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-table-of-contents-rail-width sys.border.hairline 1px
--tanrei-comp-table-of-contents-row-height-compact — 32px
--tanrei-comp-table-of-contents-row-height-regular — 44px
--tanrei-comp-table-of-contents-row-indent sys.space.space150 12px
--tanrei-comp-table-of-contents-row-padding-block-compact — 6px
--tanrei-comp-table-of-contents-row-padding-block-regular — 12px
--tanrei-comp-table-of-contents-row-padding-inline sys.space.space150 12px
--tanrei-comp-table-of-contents-row-shape sys.shape.corner.extra-small 3px
--tanrei-comp-table-of-contents-title-color sys.color.on-surface #1b1b1b
--tanrei-comp-table-of-contents-compact-supporting-text-typescale sys.typescale.body-small 400 13px/1.5385
--tanrei-comp-table-of-contents-label-typescale sys.typescale.body-small 400 13px/1.5385
--tanrei-comp-table-of-contents-title-typescale sys.typescale.label 500 14px/1.4286

フィクスチャ

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

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

ガイドライン

目次にするかどうか

1 画面より長く、見出しで区切られているページに使います。見出しのないページには要りません。項目がなければ、コンポーネントは何も描かず、空のランドマークも残しません。ページからページへの移動にはナビゲーションドロワーを、1 つの作業の手順にはステップを使います。

レイアウトが決めること

コンポーネントは与えられた幅いっぱいに広がり、自分の置き場所については何も決めません。次のことは、目次を置くレイアウトの側で決めます。

  • どこに置くか。また、ページをスクロールしても見える位置にとどめること(たとえば sticky の列)。
  • どのウィンドウでどの形を見せるか。テキストの横に置く場所がないところでは、presentation="compact" を設定します。
  • 長いリストを囲む、スクロールする列。その場合、コンポーネントは現在の行がその列の中で見えているようにします。

密度

density 1 行に収まる行の高さ 用途
regular(既定) 44。ポインター用のターゲットの高さです ほとんどのページ。
compact 32 ポインターで操作するプロダクトで、テキストの横に置く長い見出しのリスト。

compact の行は、ポインター用のターゲットより低くなります。名前を指定して選ぶもので、既定になることはないので、これを認めています。タッチでは、どちらの密度でも行の高さはタッチターゲットの高さです。テキストスタイルと字下げは、どちらの密度でも同じです。

現在の項目の決まり方

リンクで見出しへ移動したとき、ブラウザーはその見出しを決まった位置に止めます。現在の項目は、見出しがその位置まで届いている項目のうち、ページの順で最後のものです。コンポーネントは、その位置を利用者側の CSS から読み取ります。

html { scroll-padding-top: 4rem; }     /* 上部に固定しているものの高さ */
h2, h3 { scroll-margin-top: 1.5rem; }  /* 見出しが自分の上に空けておく余白 */
場合 現在の項目
最初の見出しより前 なし。ページの先頭を指す項目を渡していれば、その項目です。
ページの末尾 最後の項目。最後のセクションがウィンドウより短いと、その位置にはいつまでも届かないためです。
項目が選ばれた その項目。すぐに現在の項目になり、読み手が次に自分でスクロールするまで変わりません。
アドレスが見出しを指している その項目。同じように保たれます。
見出しより上のコンテンツが読み込まれた、または折りたたまれた 決め直します。

コンポーネントがページをスクロールすることも、アドレスを書き換えることもありません。見出しまで滑らかに動くか、一度に移るかは、利用者側の scroll-behavior で決まります。

1 つずつ表示する手順のように、セクションが 1 つのスクロールするビューの中の場所ではないときは、tracking="manual" を設定し、current を自分で設定します。

コンパクトな形

presentation="compact" は、塗りのある 1 つのブロックで、その中に行が 1 つあります。行には、タイトル、現在のセクションの名前、シェブロンが並びます。この行はボタンで、押すと、同じリストがその下に表示されます。

  • 項目を選ぶと、リストが閉じ、リンクをたどります。
  • Escape を押すとリストが閉じ、フォーカスは行に戻ります。
  • 要素に max-block-size を指定すると、リストはその中でスクロールし、行は動きません。
  • オーバーレイではありません。スクリムは描かず、外側を押しても閉じません。目次をテキストの上に重ねておき、外側を押したら閉じるようにしたいレイアウトでは、利用者側で open を false にします。

スクリプトが読み込まれるまでの間、コンパクトな形はリスト全体を表示します。それを一瞬でも見せたくないレイアウトでは、tanrei-table-of-contents[presentation="compact"]:not(:defined) を隠します。

推奨

  • label は、プロダクトの言語で書きます。1 つのページに目次が 2 つあるときは、それぞれに別の名前を付けます。
  • 目次に見せたい見出しを渡します。ふつうはレベル 2 とレベル 3 の見出しです。
  • 最初の見出しより前のテキストが長いときは、ページの先頭を指す項目を渡します。

非推奨

  • 項目にコントロール、タグ、アイコンを置かない。ラベルは見出しのテキストです。
  • 3 階層より深く入れ子にしない。狭い列では、ラベルを置く場所がなくなります。
  • プロダクトのナビゲーションとして使わない。現在の項目が伝えるのは「このページのこの部分」であって、「このページ」ではありません。

アクセシビリティ

ロール
navigation
ラベル付け
A navigation landmark named by label ("On this page"), in the product's language; a page with a second table of contents gives each another name. Inside it a list of links, each named by its item's label, sub-headings as a list inside their heading's item. The current item carries the current-location state and nothing is added to its name. In the compact presentation the disclosure row is a button named by label, described by the current item's label, with its expanded state. The title is drawn text and the landmark's name; it is a heading only where the product gave one.
最小ターゲット
見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px

キーボード操作

キー 動作
Tab / Shift+Tab Moves focus through the items in order, each one a stop, as through any list of links; in the compact presentation to the disclosure row, and through the items only while the list is shown.
Enter On an item: follows the link to its heading. On the disclosure row: shows or hides the list.
Space On the disclosure row: shows or hides the list. On an item it does what it does on any link (on the web it scrolls the page).
Escape In the compact presentation, while the list is shown and focus is on the row or in the list: hides the list and returns focus to the row.

備考

  • The current item is exposed as a location, not as a page. Every item of a table of contents leads to the page the person is already on, so the state that says "the current page" would be true of all of them and is the one the product's navigation uses for the page itself; the state for the current location within a context says what is meant: this part, of this page. The bare true value says less than either. A screen reader that does not know the value announces the item as current, which is still right.
  • A change of the current item is not announced (TOC-8). It follows from the person's own scrolling or from the link they chose, and a live region would narrate every heading a reader scrolls past. The state is there when they move to the list.
  • The items are plain links in the focus order, one stop each, with no arrow-key movement of their own: a table of contents is a list of links in a landmark, which assistive technology already lets people list, skip and move through, and a composite widget would take those keys away. A product places a skip link before a long one, or places the table of contents after the content in the reading order.
  • Current is never told by color alone (TOC-7): the marker is a stroke heavier than the rail in primary, which the token invariants hold to 4.5:1 on every surface role of the default system (13.3:1 at the least today; 3.1:1 at the least in the shipped presets), and the label is heavier by the system's emphasis. The rail is decorative and is not held to 3:1, as a divider's line is not. Item labels are secondary text on a surface and the current label primary text, both held to 4.5:1 by the token invariants.
  • The target (TOC-24), WCAG 2.5.8. A row's hit area is the list's whole width and the row's height, with no space between rows. For touch a row is drawn at least the touch target high in both densities. For a fine pointer a regular row, the default, is the pointer target high (44 in the default system). The compact density is lower than the pointer target (32), by design: it is the density a product asks for by name where a long list of headings must fit beside the text, it stays above the 24 by 24 the criterion asks, and its rows reach across the list's whole width. A product that cannot accept that uses regular.
  • The items are not underlined (TOC-6). An underline is what tells a link from the text beside it in a sentence (WCAG 1.4.1); a row of a list inside a navigation landmark has no text beside it to be told from, as a navigation drawer's destination has not.
  • Keyboard focus after choosing an item goes with the page (TOC-28): the next Tab starts from the chosen section. The table of contents never pulls focus back to itself and never moves it into the content.
  • Text must remain readable at 200% text size: labels wrap and rows grow (TOC-33); only the current section's name in the compact row is ever cut, and it is whole in the list below it.
  • Motion: the table of contents moves nothing but its own compact list and its state changes, each at once where motion is reduced (TOC-7, TOC-23, TOC-26). Whether the page glides to a heading is the product's setting, not this component's (TOC-14).
  • A page with no headings has no table of contents: no empty landmark is left for assistive technology to find (TOC-32).
  • How a state looks is a value decision of the active design system (a state-layer mix in Tanrei, a dim or no hover at all elsewhere). The table of contents reads the state's tokens and never computes the effect itself.

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

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

  • label を名前とするナビゲーションのランドマークで、その中にリンクの入れ子のリストがあります。
  • 現在の項目には aria-current="location" が付きます。現在の項目が変わっても読み上げないので、スクロールのたびに読み上げが入ることはありません。
  • どの項目も、フォーカスの順序に入っているリンクで、それぞれが 1 つの止まる位置です。Enter でリンクをたどります。矢印キーでの移動はないので、覚えることもありません。
  • レール、マーカー、シェブロンは、支援技術から隠されます。
  • タイトルは、ランドマークの名前として 1 度だけ伝わります。見出しになるのは、title スロットに見出しを渡したときだけです。
  • コンパクトな形の行は、展開状態を持つボタンです。名前は label で、説明は現在のセクションの名前です。
  • 項目を選んだ後、フォーカスはページの側にあります。次に Tab を押すと、選んだ見出しから先へ進みます。
  • フォーカスリングは行の内側に描くので、スクロールする列に切り取られません。
  • 現在の項目を、色だけで示すことはありません。マーカーは線で、ラベルは太くなります。
  • 現在の項目が変わっても、リストは動きません。
  • テキストを 200% にすると、ラベルは折り返し、行は高くなります。途中で切れることがあるのは、コンパクトな形の行にある現在のセクションの名前だけです。
  • 強制カラーモードでは、項目はリンクの色に、現在の項目のラベル、レール、マーカーはテキストの色になります。
  • 空の label、ラベルのない項目、同じ href を持つ 2 つの項目、行き先のない href、未知の値、3 階層を超える入れ子は、デバッグビルドで報告します。

利用者側で行うこと

  • 目次に載せるすべての見出しに id を付け、項目の href に同じフラグメントを書きます。
  • 上部に固定しているものがあれば、その高さを scroll-padding-top で宣言します。リンクでページがそこへ動いたときに、見出しがその下に隠れないようにするためです。
  • 読み手が動きを減らす設定にしているときは、スムーズスクロールをオフにします。
  • 長い目次は、読む順序でコンテンツの後ろに置くか、その前にスキップリンクを置きます。