タブビュー
自分のタブの並び、またはほかの場所に置いたタブの並びと連動して、選ばれたタブのビューを表示します。スワイプでビューを切り替えられ、タブのインジケーターが指に合わせて動きます。
- カテゴリ
- ナビゲーション
- トークン
comp.tab-view- レベル
- 合成
- 構成要素
- Tabs
- Web
tanrei-tab-viewschedule - SwiftUI
TanreiTabViewradio_button_unchecked - Jetpack Compose
TanreiTabViewradio_button_unchecked - Flutter
TanreiTabViewradio_button_unchecked - React Native
TabViewradio_button_unchecked
生成元 docs/status/status.json, specs/tab-view.spec.json
タブビューは、タブごとに 1 つのパネルを持ち、選ばれたタブのパネルを表示します。タブを選ぶと、短いずれとフェードとぼかしでビューが切り替わります。ビューを横にスワイプしても切り替えられ、タブのインジケーターは指に合わせて動き、ビューが止まったタブに落ち着きます。動きを減らす設定では、ずれとぼかしのないクロスフェードになり、それも指に合わせて進みます。
自分のタブの並びを持つタブビュー:タブを選ぶか、タッチかペンでパネルをスワイプします
今週が期限のタスクが 3 件あります。
1 時間前に山田さんが計画にコメントしました。
ワークスペースの全員に公開されています。
タブビューには 2 つの形があります。
- 自分のタブの並びを持つ形(既定)。 ビューは上端にタブの並びを描き、そのすぐ下にパネルを置きます。各パネルが自分のタブの
label、icon、labelHidden、disabledを持つので、1 つの宣言でタブとビューの両方ができます。ビューのvariant、size、track、fullWidth、label、group、rememberAsは、その並びのものです。並びはタブそのもので、トラックもインジケーターも、トークンもバリアントも振る舞いも同じなので、タブの見た目を変えるレイヤはこの並びの見た目も変えます。切り替えのときに、並びが動いたり、フェードしたり、ぼけたりすることはありません。 - ほかの場所のタブとつなぐ形。
tabsで、アプリバーや固定ヘッダーに置いたタブの並びとつなぎます。tabsを設定している間は、そのタブが見つからなくても、ビューは自分の並びを描かず、並びのプロパティとパネルのタブのプロパティは使いません。
自分の並びを持つ形では、タブを選んでもスワイプしても、ビューが選択のたびに change を発火し、並び自身の change は利用者側に届きません。つなぐ形では、タブを選んだときはタブが、スワイプしたときはビューが change を発火します。どの選択も、知らされるのは 1 回です。
同じ group を指定したタブビューは同じ選択を示し、rememberAs はその選択を端末に保存します。このサイトのコンテンツのタブがその例です。プラットフォーム別のサンプルはすべて、ひとつのグループに属するセカンダリーのタブビューで、選択はサイト自身のキーで記憶されます。
Web では <tanrei-tab-view> で、子に <tanrei-tab-panel> を持ちます。これは、タブの並びが自分のパネルに使うのと同じ要素です。選択中のパネルはビューの value で、タブの value と常に同じ値に保たれます。どちらかを設定すると、イベントなしでもう一方も同じ値になります。つなぐ形では、tabs に、同じドキュメントまたはシャドウルートにある <tanrei-tabs> の id を指定します。swipeable は、逆の意味の no-swipe と書きます。HTML の真偽値の属性は false が既定だからです。
SwiftUI、Compose、Flutter、React Native は、タブビューをまだ実装していません。実装されるまで、これらのプラットフォームでは、タブのページにあるとおり、タブの並びが選択中のパネルを表示します。スワイプはありません。
<!-- 自分の並びを持つ形(既定):各パネルが自分のタブを宣言します。 -->
<tanrei-tab-view label="プロジェクト">
<tanrei-tab-panel value="overview" label="概要" icon="dashboard">…</tanrei-tab-panel>
<tanrei-tab-panel value="activity" label="アクティビティ" icon="history">…</tanrei-tab-panel>
<tanrei-tab-panel value="billing" label="請求" icon="payments" disabled>…</tanrei-tab-panel>
</tanrei-tab-view>
<!-- つなぐ形:タブをほかの場所(アプリバー、固定ヘッダー)に置きます。ビューは並びを描きません。 -->
<tanrei-tabs id="project-tabs" label="プロジェクト">
<tanrei-tab value="overview" label="概要"></tanrei-tab>
<tanrei-tab value="activity" label="アクティビティ"></tanrei-tab>
</tanrei-tabs>
<tanrei-tab-view tabs="project-tabs">
<tanrei-tab-panel value="overview">…</tanrei-tab-panel>
<tanrei-tab-panel value="activity">…</tanrei-tab-panel>
</tanrei-tab-view>
<!-- 同じ group を指定したビューは 1 つの選択を示します。remember-as は、利用者側で決めたキーで選択を端末に保存します。 -->
<tanrei-tab-view variant="secondary" group="platform" remember-as="my-docs-platform" label="プラットフォーム">
<tanrei-tab-panel value="web" label="Web">…</tanrei-tab-panel>
<tanrei-tab-panel value="flutter" label="Flutter">…</tanrei-tab-panel>
</tanrei-tab-view>
<script type="module">
import '@tanrei/web/components/tab-view'; // タブとパネルも定義します
// change は選択ごとに 1 回です。自分の並びを持つ形:タブでもスワイプでも、ビューが発火します。
// つなぐ形:タブではタブが、スワイプではビューが発火します。グループ:操作されたビューだけが発火します。
document.addEventListener('change', (e) => console.log(e.target.localName, e.detail.value));
</script>import { Tab, TabPanel, Tabs, TabView } from '@tanrei/react';
// 自分の並びを持つ形:タブでもスワイプでも、すべての選択が 1 つの onChange に届きます。
<TabView label="プロジェクト" value={view} onChange={(e) => setView(e.detail.value)}>
<TabPanel value="overview" label="概要" icon="dashboard"><Overview /></TabPanel>
<TabPanel value="activity" label="アクティビティ" icon="history"><Activity /></TabPanel>
</TabView>
// つなぐ形:タブの選択はタブが、スワイプはビューが知らせます。両方から状態を更新します。
<Tabs id="project-tabs" label="プロジェクト" value={view} onChange={(e) => setView(e.detail.value)}>
<Tab value="overview" label="概要" />
<Tab value="activity" label="アクティビティ" />
</Tabs>
<TabView tabs="project-tabs" onChange={(e) => setView(e.detail.value)}>
<TabPanel value="overview"><Overview /></TabPanel>
<TabPanel value="activity"><Activity /></TabPanel>
</TabView>
// 共有し、記憶する選択。noSwipe は、仕様の swipeable の逆です。
<TabView variant="secondary" group="platform" rememberAs="my-docs-platform" label="プラットフォーム" noSwipe>
<TabPanel value="web" label="Web"><WebSample /></TabPanel>
<TabPanel value="flutter" label="Flutter"><FlutterSample /></TabPanel>
</TabView>アイコンと無効なタブのある、幅を等分するセカンダリーの並び。離れた場所に置いた並びとつないだビュー
3 つのサービスがすべて稼働中です。
2 時間前にデプロイしました。
請求は設定されていません。
1 日目:到着して、旧市街を歩きます。
駅の近くに 4 か所を保存しました。
美術館は月曜日が休館です。
構成要素
| パーツ | 説明 |
|---|---|
1 tabs · 任意 | The row of tabs the Tab view draws above its container while it is not linked to a row placed elsewhere (TBV-16): a Tabs, whole (tab list, tabs, active indicator, underline and divider), drawn from the tabs' own tokens, with one tab for each panel, named and marked as the panel says. Linked to another row, the Tab view draws none (TBV-17). |
2 container | The region that holds the panels, below the row when the Tab view draws one: as wide as the Tab view and as tall as the shown panel. It draws nothing of its own, takes the swipe, and clips the panels to its corners while they travel and blur. The row is outside it and is never moved, faded or blurred by a switch. |
3 panel | One view, matched to the tab of the same value. While the Tab view draws its own row, the panel also says what its tab shows (a label, an icon) and whether it is disabled. One panel is shown at rest; during a switch the old and the new panel are drawn in the same place, the new one over the old. Its content is the product's. |
4 focus-ring | Gap-then-ring indicator shown for keyboard focus on a panel that holds nothing focusable, drawn inside the container's edge. |
プロパティ
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
value | string | null | The value of the selected tab, whose panel is shown. The Tab view keeps it equal to its tabs' value, whether it draws them or is linked to them. null shows the panel of the tab the tabs select while theirs is null (the first enabled tab). |
variant | primary | secondary | tonal | "primary" | The variant of the row the Tab view draws itself, with the Tabs' meaning: primary for the main views of a screen, secondary for views within one of them, tonal for the quiet row of a sample or a setting. A design system may add values to the tabs' axis, and the row takes them. Ignored while the Tab view is linked to a row placed elsewhere, which has its own. |
size | small | medium | "medium" | The size of the row the Tab view draws itself, with the Tabs' meaning: medium for the views of a screen, small for a row in a card's or a sample's header. A design system may add values to the tabs' axis, and the row takes them. Ignored while the Tab view is linked to a row placed elsewhere, which has its own. |
track | boolean | null | Whether the row the Tab view draws itself draws its track, with the Tabs' meaning: false draws none, true draws it, and null, the default, takes comp.tabs.track.visible, the system's choice. Ignored while the Tab view is linked to a row placed elsewhere, which has its own. |
fullWidth | boolean | false | How the row the Tab view draws itself lays out its tabs, with the Tabs' meaning: false (hug) sizes each tab to its label and scrolls the row when they do not fit; true (fill) shares the row's width among the tabs. The Tab view itself is always as wide as its parent. Ignored while the Tab view is linked to a row placed elsewhere. |
label | string | "" | The accessible name of the row the Tab view draws itself: what the views are views of. Needed when a screen has more than one row of tabs. Ignored while the Tab view is linked to a row placed elsewhere, which is named by its own label. |
tabs | string | "" | Empty (the default): the Tab view draws its own row of tabs above its panels, one per panel. Set: the Tab view is linked to a Tabs placed elsewhere (in an app bar, a sticky header) and draws no row; those tabs name its panels, move between them without a swipe and draw their indicator where a swipe has brought it. How a platform names the link is in its mapping. |
swipeable | boolean | true | Lets a person switch panels by dragging the shown panel sideways. False leaves the switch to the tabs alone, for panels whose content is itself dragged sideways (a map, a drawing surface). |
transition | auto | slide | fade | none | "auto" | The effect of switching views: the form in which the shown panel gives way to the next. slide: the panels travel, and fade and blur on the way, as the system's transition values say, and follow a swipe. fade: the two panels crossfade in place. none: the new panel is shown whole in one frame, and the row the Tab view draws puts its indicator on the new tab in that same frame (TBV-22, TBV-23). Auto takes comp.tab-view.motion.transition, the design system's answer (slide in the default system). Where motion is reduced slide is drawn as fade, and nothing is ever added to fade or none. It applies whether the Tab view draws its row or is linked to one; a linked row keeps its own transition. |
group | string | "" | The group of the row the Tab view draws itself, with the Tabs' meaning: Tab views and rows of tabs that name the same group show the same choice, and a view that has no panel of that value keeps its own. Empty (the default): the view's choice is its own. Ignored while the Tab view is linked to a row placed elsewhere, which names its own group. |
rememberAs | string | "" | The key under which the row the Tab view draws itself keeps a person's choice on their device and restores it before the view is first drawn, with the Tabs' meaning. Empty (the default): nothing is read or written. Ignored while the Tab view is linked to a row placed elsewhere, which names its own key. |
イベント
| イベント | 説明 |
|---|---|
change | The user selected another panel; carries its value, which value and the tabs' value now hold. The Tab view announces every selection made on its own row and every swipe. While it is linked, a selection made on the linked tabs is announced by their own change instead, so every selection is announced once. |
アイテム
このコンポーネントが繰り返すアイテムの種類です。フィクスチャはアイテムを順に並べ、各プラットフォームはそれぞれの流儀で種類ごとに名前を付けます。
panel
One view of the Tab view: the content shown while the tab with its value is selected, and, for the row the Tab view draws itself, what that tab shows.
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
value | string | "" | The value of the tab that shows this panel. Unique within the Tab view; required. |
label | string | "" | The text of this panel's tab in the row the Tab view draws itself, and the accessible name of both the tab and the panel. Required there, even when labelHidden is true; not used while the Tab view is linked, where the linked row's own tab names the panel. |
icon | string | "" | The name of an icon (a glyph of the system's icon set) for this panel's tab in the row the Tab view draws itself. Give every panel an icon, or none. Not used while the Tab view is linked. |
labelHidden | boolean | false | Draws this panel's tab as its icon alone and keeps label as its accessible name (an icon-only tab), in the row the Tab view draws itself. Not used while the Tab view is linked. |
disabled | boolean | false | Makes this panel's tab disabled in the row the Tab view draws itself: a person cannot select it, and a swipe skips its panel. Not used while the Tab view is linked, where the linked row's own tab says whether it is disabled. |
| 仕様 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| アイテム | tanrei-tab-panel | TanreiTabPanel | TanreiTabPanel | TanreiTabPanel | TabPanel |
value · プロパティ | value | tag | value | value | value |
label · プロパティ | label | title | label | label | label |
icon · プロパティ | icon | icon | icon | icon | icon |
labelHidden · プロパティ | labelHidden | labelStyle | labelHidden | labelHidden | labelHidden |
disabled · プロパティ | disabled | disabled | enabled | enabled | disabled |
| 備考 | The same element the tabs' own panels are: <tanrei-tab-panel value="overview" label="Overview" icon="dashboard">, a child of tanrei-tab-view, with label-hidden and disabled attributes, as tanrei-tab has them. label, icon, label-hidden and disabled are read only by a tanrei-tab-view that draws its own row; a panel of a tanrei-tabs, or of a linked view, ignores them. | TanreiTabPanel(_ title:, icon:) { … }.tag(value) in the view's builder, as the tabs' TanreiTab is; .labelStyle(.iconOnly) is labelHidden and .disabled(_:) is disabled. A linked view's panels may omit the title: TanreiTabPanel { … }.tag(value). | TanreiTabPanel(value, label, icon, labelHidden, enabled) { … } called in the TanreiTabView { } content, a scope that collects the panels (as a lazy list's content does) so that the row can be drawn from their labels before a panel is composed. enabled is the inverse flag. | TanreiTabPanel<T>(value:, label:, icon:, labelHidden:, enabled:, child:) entries of the view's panels: list, as the bar's TanreiTab<T> entries are; enabled is the inverse flag. | <TabPanel value="overview" label="Overview" icon="dashboard">…</TabPanel> children of <TabView>; labelHidden and disabled as on the row's Tab. |
状態
振る舞い
EARS 記法による要件です。各プラットフォームの実装は、この ID に対応するテストで検証されます。要件の原文は仕様ファイルのものをそのまま掲載しています。
| ID | 要件 |
|---|---|
TBV-1 | THE Tab view SHALL show the panel whose value is the value of its tabs' selected tab, its tabs being the row it draws itself (TBV-16) or the row it is linked to (TBV-17) (TAB-11: the first enabled tab while their value is null), and hide every other panel; THE Tab view SHALL keep its own value equal to its tabs' value, and WHEN either of them is set THE Tab view SHALL set the other to the same value without emitting change. |
TBV-2 | WHEN its tabs' value changes while their indicatorOffset is 0 (the user selected a tab, by activating it or by releasing a drag across the row on it, TAB-1, TAB-12 and TAB-29, or the product set it) THE Tab view SHALL switch from the shown panel to the newly selected tab's panel as TBV-5 and TBV-6 say, in the form its transition names (TBV-22); WHILE it is linked THE Tab view SHALL NOT emit change for that selection, which the linked tabs have announced themselves, and WHILE it draws its own row it SHALL announce it as TBV-18 says. WHERE their value changes because they follow a choice of their group that was made on another member, or one restored from storage, THE Tab view SHALL show the panel at once instead, as TBV-20 says. |
TBV-3 | WHILE swipeable is true, WHEN the user drags the shown panel by touch or pen past the platform's touch slop, further sideways than up or down, THE Tab view SHALL take the drag and track it with a progress p, the drag's sideways distance divided by the width of its container and kept between 0 and 1: toward the next enabled tab of its tabs when the drag moves toward the start edge, and toward the previous enabled tab when it moves toward the end edge. In every frame of the drag THE Tab view SHALL draw the switch to that tab's panel at p (TBV-5) and set its tabs' indicatorOffset to p toward the next tab or to −p toward the previous one; WHERE there is no enabled tab in the drag's direction THE Tab view SHALL leave the panel at rest and indicatorOffset at 0. A drag that is further up or down than sideways when it passes the slop SHALL be left to vertical scrolling for its whole length. A drag that starts on the row the Tab view draws itself is the row's (its tabs are pressed, its active indicator follows the drag, TAB-28, and it scrolls sideways itself) and SHALL NOT be taken; WHILE its tabs follow a drag of their own, in its own row or in the linked row, THE Tab view SHALL take no drag and SHALL leave the shown panel at rest until that drag's release selects a tab (TBV-2): the indicator changes continuously under the finger, but which panel is shown is not decided before the release. |
TBV-4 | WHEN the user releases a drag the Tab view has taken THE Tab view SHALL commit it if p is at least 0.5 or the release is a fling toward the tab, and cancel it otherwise, a fling away from the tab always cancelling; a fling is a release faster than the platform's minimum fling velocity (Android's ViewConfiguration.getScaledMinimumFlingVelocity, Flutter's kMinFlingVelocity) or, where the platform names none (the web, SwiftUI, React Native), 0.5 points per millisecond. A release's velocity is the pointer's over its last moves: WHERE the pointer has not moved for longer than the time after which the platform's velocity tracker takes a pointer as stopped (40 milliseconds in Android's VelocityTracker, Jetpack Compose's VelocityTracker and Flutter's VelocityTracker) or, where the platform names none (the web, SwiftUI, React Native), 40 milliseconds before the release THE Tab view SHALL take the release as having no velocity, so that a drag that stops where it is wanted and is let go there is no fling, however fast it moved before it stopped. To commit THE Tab view SHALL set its value and its tabs' value to that tab's value and emit change exactly once with it, re-express indicatorOffset from the new tab (1 − p toward the tab it left) in the same frame, and finish the switch while bringing indicatorOffset to 0 over comp.tab-view.motion.switch-duration with comp.tab-view.motion.switch-easing, starting from where the release left them. To cancel THE Tab view SHALL bring the switch back to p = 0 and indicatorOffset to 0 over the same motion and emit nothing. |
TBV-5 | WHILE a switch from panel A to panel B is at progress p THE Tab view SHALL draw both panels in the same place, B over A, with a travel D of comp.tab-view.transition.distance plus comp.tab-view.transition.distance-fraction times the width of its container: A moved p × D toward the edge it leaves by (the start edge when B's tab comes after A's among its tabs, the end edge otherwise), at an opacity of 1 − p × (1 − comp.tab-view.transition.opacity) and blurred by p × comp.tab-view.transition.blur; B moved (1 − p) × D from the opposite edge, at an opacity of 1 − (1 − p) × (1 − comp.tab-view.transition.opacity) and blurred by (1 − p) × comp.tab-view.transition.blur. The travel SHALL follow p as eased, an overshoot included; the opacity and the blur SHALL follow p clamped between 0 and 1. THE Tab view SHALL clip both panels to its container, its corners rounded by comp.tab-view.container.shape, so that neither travel nor blur draws outside it or over the row it draws itself; WHERE a platform cannot blur a view THE Tab view SHALL draw the travel and the fade without the blur and say so in its README. |
TBV-6 | WHEN a switch starts from a selection on its tabs or by the product (TBV-2) THE Tab view SHALL run p from 0 to 1 over comp.tab-view.motion.switch-duration with comp.tab-view.motion.switch-easing; WHEN the selection changes again, or the user starts a drag, before a switch has ended THE Tab view SHALL end that switch at once, as if it had finished, and start from the panel it switched to. WHEN the Tab view first appears, or its panels are added, removed or given other values, or it is linked or unlinked (TBV-17), THE Tab view SHALL show the selected tab's panel at once, without a switch. |
TBV-7 | WHERE motion is reduced THE Tab view SHALL draw every switch as TBV-5 says with comp.tab-view.transition.distance, comp.tab-view.transition.distance-fraction and comp.tab-view.transition.blur taken as 0, so that only the fade is left: during a drag the panels crossfade with p, and a switch from a selection, and the rest of a switch after a release, SHALL run over comp.tab-view.motion.fade-duration with comp.tab-view.motion.fade-easing. Its tabs' indicatorOffset SHALL still follow the drag, which the person's finger moves, and SHALL return to 0 at once on release. |
TBV-8 | THE Tab view SHALL NOT take a drag that starts on its own row or while its tabs follow a drag of their own (TBV-3), on content that handles sideways drags itself (a region that scrolls sideways, a slider, a carousel, a text field, selected text or its handles) or that is made by mouse, trackpad or wheel, and WHILE swipeable is false THE Tab view SHALL take no drag at all; in every such case the panels switch only through the tabs (TBV-2). |
TBV-9 | THE Tab view SHALL expose the shown panel with the tab panel role, named by its tab and associated with it where the platform can express that (the tab controls the panel; the panel is labelled by the tab), whether the tab is in the row it draws itself or in the row it is linked to, SHALL hide every other panel from assistive technology and from focus, including a panel that a switch or a drag still draws, SHALL move that exposure to the new panel when value changes rather than when the switch's drawing ends, and SHALL make a shown panel with no focusable content itself focusable so that Tab reaches it. |
TBV-10 | WHEN a switch hides the panel that holds keyboard focus THE Tab view SHALL move focus to its tabs' selected tab. WHEN a panel with no focusable content receives keyboard focus THE Tab view SHALL show the focus ring inside the container's edge, its corners following comp.tab-view.container.shape, as sys.behavior.focus-style names (ring or glow), without animation; focus gained by pointer or touch SHALL NOT show it; a platform that cannot draw a choice SHALL fall back to the first value and state that in its README. |
TBV-11 | THE Tab view SHALL be as wide as its parent and, unless the product sizes it through the platform's own sizing, as tall as the row it draws itself (none while it is linked) plus its shown panel; WHILE a switch draws two panels its container SHALL be as tall as the taller of them, and SHALL take the new panel's height at once when the switch ends. THE Tab view SHALL place its own row at its top edge, across its whole width, with the container directly below it and no space between them. WHILE the product sizes it THE Tab view SHALL keep its own row at its own height at the top, and make each panel fill the rest and scroll up and down inside it, below the row. |
TBV-12 | THE Tab view SHALL keep a hidden panel and its state (its scroll position, what was typed into it), so that it is shown as it was left, and SHALL NOT discard or recreate a panel because it is hidden, nor because its tab in the row it draws itself changes. |
TBV-13 | WHEN the layout direction is right-to-left THE Tab view SHALL mirror the drag and the travel with the reading order: a drag toward the start edge, which is the right, moves toward the next tab, and a panel leaves by the start edge when the tab it gives way to comes after its own; THE Tab view SHALL NOT mirror a panel's content. Its own row SHALL mirror as TAB-21 says. |
TBV-14 | WHEN tabs names a row that is not there (on a platform that links by an object, a link that no row uses) THE Tab view SHALL show the panel whose value equals the view's value (the first panel while the view's value is null), take no drag, expose no panel with the tab panel role, draw no row of its own (TBV-17), and in debug builds report that its panels have no tabs; WHEN the selected tab has no panel THE Tab view SHALL show none and in debug builds report the value. WHILE the Tab view draws its own row and holds no panel THE Tab view SHALL draw that row with no tab and in debug builds report that it has no panels. |
TBV-15 | WHEN a panel's value is empty, repeats another panel's, or, while the Tab view is linked, is the value of no tab of the linked row THE Tab view SHALL still hold it, show only the first panel with a given value, give its own row no tab for any other, and in debug builds report the panel; WHEN the linked row holds panels of its own THE Tab view SHALL show and associate its own panels in their place, keep the tabs' panels hidden, and in debug builds report the tabs' panels. |
TBV-16 | WHILE tabs is empty THE Tab view SHALL draw its own row of Tabs above its container (TBV-11) and be linked to that row as it would be to a row placed elsewhere: one tab for each panel that TBV-15 shows, in the panels' order, whose value, label, icon, labelHidden and disabled are the panel's; the row's variant, size, track, fullWidth, label, group and rememberAs SHALL be the Tab view's own, its transition what TBV-23 says, and its value the Tab view's value. The row SHALL be a Tabs in every respect, drawn from the tabs' tokens of its variant and size with every TAB behaviour (among them the press answer of TAB-8 to TAB-10, the keyboard of TAB-12, the names of TAB-13 and TAB-22, and the indicator of TAB-17 and TAB-26), and SHALL change in the same frame as the panels it is drawn from are added, removed, reordered or given another value, label, icon, labelHidden or disabled. |
TBV-17 | WHILE tabs names a row THE Tab view SHALL draw no row of its own, whether or not the named row is there (TBV-14), and SHALL use none of its variant, size, track, fullWidth, label, group and rememberAs or of its panels' label, icon, labelHidden and disabled, which the linked row's own props and tabs replace; WHEN tabs names a row while variant, size, track, fullWidth, label, group or rememberAs differ from their defaults THE Tab view SHALL in debug builds report that they apply only to a row it draws itself. WHEN tabs changes from empty to a row, or back, THE Tab view SHALL remove or draw its own row at once, keep its value (giving it to the newly linked row, or taking that row's while its own is null) and show the selected tab's panel at once (TBV-6). |
TBV-18 | WHILE the Tab view draws its own row, WHEN the user selects another tab on that row (TAB-1, TAB-12, TAB-29) THE Tab view SHALL set its value to that tab's value and emit change exactly once with it, in the same frame as the row sets its own value, and SHALL NOT let the row's own change reach the product as a second announcement; together with TBV-4 every selection a person makes on a Tab view with its own row, by a tab or by a swipe, SHALL be announced once, by the Tab view. |
TBV-19 | THE Tab view 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 Tab view is shown THE Tab view SHALL draw itself for the new direction at once, without a motion of its own, keeping the selected panel and its focus. |
TBV-20 | WHEN its tabs' value changes because their group's choice was made on another member or restored from storage (TAB-36, TAB-38, TAB-39) THE Tab view SHALL show the newly selected tab's panel at once, with none of the travel, fade and blur of TBV-5 and without the motion of TBV-6 or TBV-7, SHALL take that panel's height in the same frame (TBV-11), SHALL keep the panel it hides and its state (TBV-12), SHALL move the panels' exposure to assistive technology in that frame (TBV-9), and SHALL emit no change. WHERE that hides the panel that holds keyboard focus THE Tab view SHALL move focus to its tabs' selected tab as TBV-10 says, without scrolling anything to show it. WHERE the Tab view follows a swipe when the choice changes THE Tab view SHALL end the swipe without committing it, emit nothing for it, and show the chosen panel at rest. THE Tab view a person selected on SHALL switch as TBV-2 and TBV-6 say and announce the selection once (TBV-18, or the linked tabs' change). |
TBV-21 | WHEN a person commits a swipe (TBV-4) on a Tab view whose tabs are a member of a group and, in following that choice, other members change the size of what lies before the Tab view in a region that scrolls, THE Tab view SHALL keep its container where the person is using it, as TAB-40 says of a row: in the frame that first draws the new choice the container's top edge SHALL be at the place in the window that it had in the frame before, the scroll positions of the regions it scrolls in having been moved by the difference at once and without a scrolling motion, as far as each region can scroll. A selection made on a tab SHALL be kept in place by the row (TAB-40), whether the Tab view drew that row or is linked to it. |
TBV-22 | THE Tab view SHALL draw every switch between two panels in the form its transition names: the name transition gives, or, WHILE transition is auto, the name comp.tab-view.motion.transition holds (slide, fade or none); WHEN transition names a value the Tab view does not define THE Tab view SHALL treat it as auto and in debug builds report it. WHILE the form is slide THE Tab view SHALL switch as TBV-3 to TBV-6 say. WHILE the form is fade THE Tab view SHALL draw every switch as TBV-5 says with comp.tab-view.transition.distance, comp.tab-view.transition.distance-fraction, comp.tab-view.transition.blur and comp.tab-view.transition.opacity all taken as 0, so that the two panels crossfade in place: a switch from a selection, and the rest of a switch after a swipe is released, SHALL run over comp.tab-view.motion.fade-duration with comp.tab-view.motion.fade-easing, and during a swipe the panels SHALL crossfade with p (TBV-3). WHILE the form is none THE Tab view SHALL show the newly selected tab's panel whole in the frame the selection is made, with none of the travel, fade and blur of TBV-5 and no motion of TBV-6, SHALL take that panel's height in the same frame (TBV-11), SHALL keep the panel it hides and its state (TBV-12) and SHALL move the panels' exposure and keyboard focus as TBV-9 and TBV-10 say — the drawing TBV-20 gives a choice made elsewhere; a swipe SHALL still be taken while swipeable is true (TBV-3), SHALL leave the shown panel at rest for its whole length while its tabs' indicatorOffset follows the finger, and WHEN it is released SHALL be committed or cancelled as TBV-4 decides, the new panel shown at once and indicatorOffset brought to 0 at once. In every form THE Tab view SHALL select, emit and announce as it does under slide (TBV-4, TBV-18): the form changes what is drawn between two panels and nothing else. |
TBV-23 | WHERE motion is reduced THE Tab view SHALL draw the form slide as TBV-7 says, the form fade as TBV-22 says, and the form none as none: reduced motion SHALL never add a motion that transition took away. WHILE the Tab view draws its own row (TBV-16) and the form is none, THE Tab view SHALL give that row the tabs' transition none (TAB-43), so that the indicator is on the new tab in the frame the new panel is shown and nothing is left traveling over a view that has already changed; WHILE the form is slide or fade the row's transition SHALL be auto, the tabs' own answer (comp.tabs.motion.transition). THE Tab view SHALL NOT set the transition of a row it is linked to (TBV-17), which has its own. WHEN transition changes while a switch is drawn THE Tab view SHALL end that switch at once, as if it had finished (TBV-6), and SHALL draw the next one in the new form; changing transition SHALL NOT change value, the shown panel, keyboard focus or what is emitted. |
TBV-24 | 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 tab view 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 the tab list of the row it draws itself (TBV-16), as the tabs of that row expose what is set on theirs — 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, label is the tab list's name, over a name given so; WHILE label is empty, a name given so is the tab list's name. 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 tab view SHALL take it off that element. WHILE the tab view is linked to tabs placed elsewhere (TBV-17) it draws no row, so what the product sets on its host reaches no element: THE tab view SHALL move it all the same and report it in debug builds, and the product sets it on the linked tabs instead. |
プラットフォーム別の名前対応
パリティは API が同一であることを意味しません。同じ契約を、各プラットフォームの流儀で表現します。
| 仕様 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| コンポーネント | tanrei-tab-view | TanreiTabView | TanreiTabView | TanreiTabView | TabView |
value · プロパティ | value | selection | selectedValue | value | value |
variant · プロパティ | variant | variant | variant | variant | variant |
size · プロパティ | size | size | size | size | size |
track · プロパティ | track | track | track | track | track |
fullWidth · プロパティ | fullWidth | fullWidth | fullWidth | isScrollable | fullWidth |
label · プロパティ | label | label | contentDescription | semanticLabel | accessibilityLabel |
tabs · プロパティ | tabs | link | link | link | link |
swipeable · プロパティ | noSwipe | swipeable | swipeable | swipeable | swipeable |
transition · プロパティ | transition | transition | transition | transition | transition |
group · プロパティ | group | selection | selectedValue | value | value |
rememberAs · プロパティ | rememberAs | selection | selectedValue | value | value |
change · イベント | change | selection | onSelectedValueChange | onChanged | onValueChange |
| 備考 | Its own row: <tanrei-tab-view label="Project"> with tanrei-tab-panel children, and the attributes variant and full-width for the row. The view draws its row as a tanrei-tabs element of its own, placed as its first light-DOM child (in a tabs slot above the container) and marked as the view's, with one tanrei-tab per panel, and links itself to it as to any row. It is in the light DOM, not the shadow root, so that the tab's aria-controls and the panel's aria-labelledby point within one tree; the product does not edit it. The row's change stops at the view, which fires its own (TBV-18). Linked: <tanrei-tabs id="project-tabs"> … </tanrei-tabs> <tanrei-tab-view tabs="project-tabs">: tabs is the id of a tanrei-tabs in the same tree (as popovertarget is), and the tabsElement property sets the element itself; either one set means the view draws no row, even while the element is missing. The selected panel is the view's value, kept equal to its tabs' value in both directions; the view sets its own value before firing change, as a native control does. no-swipe is the inverse of swipeable, since an HTML boolean attribute defaults to false. The view writes its tabs' indicatorOffset property during a drag and its settle. The drag is Pointer Events on touch and pen with touch-action: pan-y on the container (not on the row), so vertical scrolling stays the browser's; the travel is a transform, the fade opacity and the blur filter: blur(), on the two panels only while a switch runs. The panels keep their place in the light DOM; a hidden one has hidden and inert. A shared choice: group and remember-as are attributes with the properties group and rememberAs, and the view writes both onto the tanrei-tabs it draws, which is the member of the group; a linked view has neither, and its tanrei-tabs carries them. A view that follows writes its panels' hidden and inert in the frame its row changes and starts no switch. For a committed swipe (TBV-21) the view measures and corrects its container's top as its row does for a tab (TAB-40). Sixty views of one group cost one read of localStorage, one write for a choice, and one measured element. size and track are forwarded to the tanrei-tabs the view draws, as variant is (track is true, false or null, with the attribute forms of the tabs'). transition is an attribute and a property (auto, slide, fade, none); auto reads --tanrei-comp-tab-view-motion-transition. fade runs the code path of prefers-reduced-motion (TBV-7) with the opacity end taken as 0; none runs the code path of a choice that came from the group (TBV-20) and sets transition="none" on the view's own row, removing it again when the form is no longer none. | Its own row: TanreiTabView(selection: Binding<V>, variant:, fullWidth:, label:, swipeable:) { TanreiTabPanel … }; the view draws a TanreiTabs above its panels and links it through a TanreiTabLink of its own. Linked: TanreiTabView(selection:, link: TanreiTabLink, swipeable:) { … }, a second initializer without the row's parameters, so that a view cannot be given both; the link is an observable object the product creates once and passes to its tabs too (TanreiTabs(…, link:)), through which the tabs report their order and disabled tabs and read the indicator offset. Writing the binding is change: a tab on the view's own row and a swipe write it; a linked row writes it through its own binding. The drag is a DragGesture on the panels that yields to vertical scrolling; the blur is .blur(radius:), converted from the token's standard deviation. SwiftUI's page-style TabView is a full-width pager without a tab row, so it is not this component. A shared choice has no parameter here: views given the same Binding show the same choice, and the product remembers it in @AppStorage, as the tabs' mapping says. A view whose selection changes that way switches as TBV-2 says for a value the product sets, with its motion: this platform cannot tell a view that follows from one the product set, so TBV-20 and TBV-21 are not reached here, and the README says so. Not built on this platform yet: size and track are the row's, with the Tabs' meaning and type; transition (.auto, .slide, .fade, .none) is the form of a switch (TBV-22): fade is the crossfade the view already draws where motion is reduced, none the change at once it already draws for a choice made elsewhere (TBV-20), and under none the view's own row is given the tabs' transition none (TBV-23). | Its own row: TanreiTabView(selectedValue, onSelectedValueChange, modifier, variant, fullWidth, contentDescription, swipeable) { TanreiTabPanel(…) { … } }; the view composes a TanreiTabs above its panels, linked through a link it remembers itself. Linked: an overload TanreiTabView(selectedValue, onSelectedValueChange, link, modifier, swipeable) { … } without the row's parameters, with val link = rememberTanreiTabLink() passed to TanreiTabs(…, link = link) too; the tabs read the indicator offset from it as a frame-time lambda, so a drag does not recompose the row. State hoisting: the caller keeps selectedValue; with its own row the view calls onSelectedValueChange for a tab and for a swipe, and linked the caller moves it from both components' callbacks. Not HorizontalPager: the travel is a short offset, not a scroll, and hidden panels stay composed (TBV-12). The blur is Modifier.blur, which draws nothing below Android 12; there the switch has no blur, as the README says. A shared choice has no parameter here: views given the same hoisted selectedValue show the same choice, and the caller remembers it in its own store, as the tabs' mapping says. A view whose selectedValue changes that way switches as TBV-2 says for a value the product sets, with its motion: this platform cannot tell a view that follows from one the product set, so TBV-20 and TBV-21 are not reached here, and the README says so. Not built on this platform yet: size and track are the row's, with the Tabs' meaning and type; transition (Auto, Slide, Fade, None) is the form of a switch (TBV-22): fade is the crossfade the view already draws where motion is reduced, none the change at once it already draws for a choice made elsewhere (TBV-20), and under none the view's own row is given the tabs' transition none (TBV-23). | Its own row: TanreiTabView<T>(value:, onChanged:, variant:, isScrollable:, semanticLabel:, swipeable:, panels: [TanreiTabPanel<T>(…)]); the view builds a TanreiTabBar<T> above its panels, linked through a TanreiTabLink<T> of its own. isScrollable is the inverse of fullWidth and defaults to true (hug), as on the bar. Linked: TanreiTabView<T>.linked(link:, value:, onChanged:, swipeable:, panels:), a named constructor without the row's parameters, with the TanreiTabLink<T> the product creates and passes to TanreiTabBar(…, link:) too, as Flutter's TabController is shared by a TabBar and a TabBarView. It replaces the existing TanreiTabBarView, which named only the content; its semanticLabels give way to the tabs, through which each panel is named by its tab with Flutter's tab panel role. The blur is an ImageFiltered blur, converted from the token's standard deviation. A shared choice has no parameter here: views given the same value from one piece of the product's state show the same choice, and the product remembers it in its own store, as the bar' mapping says. A view whose value changes that way switches as TBV-2 says for a value the product sets, with its motion: this platform cannot tell a view that follows from one the product set, so TBV-20 and TBV-21 are not reached here, and the README says so. Not built on this platform yet: size and track are the row's, with the Tabs' meaning and type; transition (auto, slide, fade, none) is the form of a switch (TBV-22): fade is the crossfade the view already draws where motion is reduced, none the change at once it already draws for a choice made elsewhere (TBV-20), and under none the view's own row is given the tabs' transition none (TBV-23). | Its own row: <TabView value onValueChange variant fullWidth accessibilityLabel swipeable> with <TabPanel> children; the view renders a <Tabs> above its panels from their props, linked through a link of its own. Linked: <TabView value onValueChange link swipeable>, where const link = useTabLink() is passed to <Tabs link={link}> too and carries the indicator offset as an animated value, so a drag does not re-render the row; the props type is a union, so that link and the row's props cannot be given together. Controlled: with its own row the view calls onValueChange for a tab and for a swipe; linked, the caller moves value from both components' callbacks. React Native has no tab panel role and cannot blur a view without a native module, so the switch draws its travel and fade only, as the README says. A shared choice has no prop here: views given the same controlled value show the same choice, and the product remembers it in its own store, as the tabs' mapping says. A view whose value changes that way switches as TBV-2 says for a value the product sets, with its motion: this platform cannot tell a view that follows from one the product set, so TBV-20 and TBV-21 are not reached here, and the README says so. Not built on this platform yet: size and track are the row's, with the Tabs' meaning and type; transition ("auto", "slide", "fade", "none") is the form of a switch (TBV-22): fade is the crossfade the view already draws where motion is reduced, none the change at once it already draws for a choice made elsewhere (TBV-20), and under none the view's own row is given the tabs' transition none (TBV-23). |
トークン
| トークン | 参照先 | 解決値(ライト) |
|---|---|---|
--tanrei-comp-tab-view-container-shape | sys.shape.corner.none | 0px |
--tanrei-comp-tab-view-motion-fade-duration | sys.motion.role.reduced | 100ms |
--tanrei-comp-tab-view-motion-fade-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-tab-view-motion-switch-duration | sys.motion.role.selection.travel | 250ms |
--tanrei-comp-tab-view-motion-switch-easing | sys.motion.easing.spatial | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-tab-view-motion-transition | — | 0 |
--tanrei-comp-tab-view-transition-blur | — | 8px |
--tanrei-comp-tab-view-transition-distance | — | 40px |
--tanrei-comp-tab-view-transition-distance-fraction | — | 0 |
--tanrei-comp-tab-view-transition-opacity | — | 0 |
フィクスチャ
基準となる描画パターンです。全プラットフォームが同じ ID でスナップショットを撮り、結果を比較できるようにしています。
variant: primary (13)
default
label: Project items: 3 second-selected
value: activity label: Project items: 3 icons
label: Project items: 3 disabled-tab
label: Project items: 3 tall-panel
value: settings label: Project items: 3 linked
tabs: project-tabs items: 3 missing-panel
value: activity tabs: project-tabs items: 2 shared-choice
value: flutter label: Platform group: fixture-platform items: 4 shared-choice-lacking
value: react label: Platform group: fixture-platform items: 2 primary-small
size: small label: Project items: 3 slide
label: Project transition: slide items: 3 fade
label: Project transition: fade items: 3 no-transition
label: Project transition: none items: 3 variant: secondary (1)
secondary-fill
variant: secondary fullWidth: true label: Project items: 3 variant: tonal (2)
tonal
variant: tonal label: Project items: 3 tonal-no-track-no-transition
variant: tonal track: false label: Platform transition: none items: 4 生成元 specs/tab-view.spec.json, tokens/comp/tab-view.tokens.json, design/tokens/
各タブが 1 つの対象の対等なビューで、頻繁に行き来するときに使います。プロジェクトの「概要」「アクティビティ」「設定」や、複数のプラットフォーム向けの同じサンプルなどです。タブビューは、タブの並びに 3 つのことを加えます。タブとパネルを 1 つの宣言で書けること、スワイプ、そしてインジケーターとパネルが一緒に行う切り替えです。
パネルがただの内容で、スワイプする必要がなければ、自分のパネルを持つタブの並びで足ります。タブビューはカルーセルでもページャーでもありません。タブごとに 1 つのパネルを表示し、1 回のスワイプで動くパネルは 1 つです。見て回るための項目の並びには、カルーセルを使います。
| 形 | 書くもの | change を発火するもの |
用途 |
|---|---|---|---|
| 自分の並びを持つ(既定) | パネルだけ。各パネルが label と icon で自分のタブを表します。 |
タブでもスワイプでも、ビュー。 | ほとんどのタブビュー。タブが内容のすぐ上にあります。 |
つなぐ(tabs) |
ほかの場所に置いたタブの並びと、その並びを指定したパネルのビュー。 | タブではタブ、スワイプではビュー。 | 固定ヘッダーの中など、内容から離れた場所にあるタブ。 |
- つないだ並びの見た目、名前、グループ、キーは、その並び自身のプロパティで決まります。つないだビューの
variant、size、track、fullWidth、label、group、rememberAsは使われず、設定されていればデバッグビルドで報告します。 - つないだビューは、読み上げ順でタブの後ろに置きます。Tab が選択中のタブからパネルへ進むようにするためです。自分の並びを持つビューは、構造上そうなっています。
- タブにバッジはないので、パネルにもありません。タブにバッジを付けたいときは、並びを自分で書いてつなぎます。
- 1 回のスワイプでパネル 1 つ。 タッチかペンで、上下よりも横に大きく動かしたドラッグは、次または前の有効なタブへ向かい、インジケーターが指に付いてきます。半分を越えて離すか、そのタブへ向けてフリングすると確定し、そうでなければ元に戻ります。マウス、トラックパッド、ホイールではスワイプできません。タブを使います。
- 無効なタブは飛ばします。 最初または最後のタブでは、パネルは動きません。
- 横にドラッグする内容は、そのドラッグを保ちます。 横にスクロールする領域、スライダー、カルーセル、テキストフィールド、選択中のテキストから始まるドラッグを、スワイプが取ることはありません。地図や描画面のように、パネル全体を横にドラッグする場合は、
swipeableを false にして、切り替えをタブに任せます。 - 並びの上のドラッグは、タブ自身のジェスチャーです。複数のタブを通り過ぎることができ、表示中のパネルは待ち、指を離すと選ばれたタブのパネルへ切り替わります。
- 切り替えの見た目はデザインシステムが決めます。 既定では、フェードとぼかしを伴う短い移動です。システムによっては、幅いっぱいのページめくりにもできます。動きを減らす設定では、フェードだけが残ります。
transition は、表示中のパネルが次のパネルに替わるときの形を指定します。
| 値 | 動作 |
|---|---|
slide |
パネルはデザインシステムが決めたとおりに移動し、スワイプにも付いてきます。 |
fade |
2 つのパネルが、その場でクロスフェードします。 |
none |
新しいパネルを 1 フレームで全体表示します。ビューが描くタブの並びも、同じフレームでインジケーターを新しいタブに置きます。 |
auto(既定) |
デザインシステムの答えに従います(comp.tab-view.motion.transition。既定のシステムでは slide)。 |
コードのように、眺めるのではなく読むパネルには none を使います。none でもスワイプはできます。パネルは動かず、インジケーターが指に付いてきて、指を離すと新しいパネルがすぐに表示されます。スワイプをやめるには、swipeable を false にします。動きを減らす設定では、slide は fade として描かれ、fade と none に動きが加わることはありません。つないだ並びは、自分の transition を保ちます。
同じグループの 2 つのタブビュー:どちらかでタブを選んでください
npm でパッケージをインストールします。
pnpm でパッケージをインストールします。
Yarn でパッケージをインストールします。
npm でビルドを実行します。
pnpm でビルドを実行します。
- 同じ問いに答えるビューには、すべて同じ
groupを指定します。グループに属するのはビュー自身の並びなので、タブのページがグループについて述べていることは、すべて当てはまります。選択はパネルのvalueで対応づけ、ラベルでは対応づけません。その値のパネルを持たないビューは、自分の選択を保ちます。このデモでは、Yarn は 1 つ目のビューにしかありません。 - 切り替えの動きを行い、
changeを発火するのは、操作されたビューだけです。ほかのビューは、移動もフェードもぼかしもなく、ただちにパネルを表示し、隠したパネルの状態を保ち、フォーカスを取りません。 - 上にあるビューの高さが変わっても、操作されたビューはウィンドウ内の同じ位置にとどまります。
rememberAsは、選択をその端末に保存し、ビューが最初に描かれる前に復元するためのキーです。キーがなければ何も保存せず、保存するのはパネルの値だけです。Web では、キーはlocalStorageのキーで、指定されたとおりに使われます。このデモはキーを指定していません。- つないだビューは、どちらのプロパティも使いません。グループとキーは、つないだ並びが自分で指定します。
- ビューは親と同じ幅で、高さは並びと表示中のパネルを合わせた高さです。パネルの高さがまちまちなのは普通のことなので、パネルが変わると、ビューの後ろに続くものが動きます。
- 下に続くものを動かしたくないときは、ビューに高さを指定します。並びは上端にとどまり、各パネルが残りを埋めて、その中でスクロールします。
- 隠れたパネルは、入力された内容やスクロール位置などの状態とともに、その場所に残ります。ビューに遅延表示のモードはありません。重いパネルの内容は、そのタブが最初に選ばれたときに描画してください。
- すべてのパネルに
labelを付けます。labelHiddenでアイコンだけを表示するときも同じです。ラベルはタブの名前になり、タブを通じてパネルの名前にもなります。 - すべてのパネルにアイコンを付けるか、どれにも付けないかのどちらかにします。
- 各パネルの
valueはビューの中で重複させず、画面を表示している間は変えないでおきます。 - 1 つの画面にタブの並びが複数あるときは、
labelでビューに名前を付けます。
- 同じバリアントのタブビューをパネルの中に入れ子にしない。セカンダリーのタブは、プライマリーのタブのビューの中に置きます。
- ほかにもビューがあることを、スワイプだけで伝えようとしない。それを伝えるのはタブで、スワイプは近道です。
- ビューが自分で描いた並びを編集しない。パネルを変えれば、並びはそれに従います。
- アプリの行き先の移動にタブビューを使わない。それはナビゲーションバーかドロワーの役割です。
- ロール
tabpanel- ラベル付け
- The row the Tab view draws itself is a tab list named by the Tab view's label, each tab named by its panel's label. Each panel is named by the tab of the same value, in the Tab view's own row or in the linked row, and each tab controls its panel. The Tab view itself is not named: it has no role of its own beyond its row and its panels.
- 最小ターゲット
- 見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px
キーボード操作
| キー | 動作 |
|---|---|
| Tab / Shift+Tab | Moves focus from the selected tab (in the Tab view's own row, or in the linked row) into the shown panel (its first focusable element, or the panel itself when it holds none), and back. |
| Right Arrow / Left Arrow, Home / End (on a tab) | Select another tab (TAB-12), and the Tab view switches to its panel. The Tab view adds no keys of its own: inside a panel the arrow keys belong to its content. |
備考
- The Tab view has no target of its own: its own row's tabs meet the minimum target as every tab does (TAB-5), and its panels hold the product's controls, which meet it themselves.
- The swipe is never the only way (WCAG 2.5.1): every panel is reached by a single tap on its tab, and by the arrow keys on the tabs. With a screen reader running, a one-finger swipe belongs to the reader; the tabs remain the way to switch panels.
- The swipe of the view and the drag across its tabs are one gesture in two places (TBV-3, TBV-4; TAB-28 to TAB-30): the tabs' active indicator follows the finger, the selection is made on release, from halfway or by a fling, a cancelled gesture returns the indicator, nothing is announced before the release, and under reduced motion the indicator still follows the finger and lands at once. They differ in reach. A swipe of the view moves to the neighbouring panel, and the panels travel with the finger. A drag across the row can pass several tabs, so the panels wait: the shown panel stays at rest during the drag, and the release switches to the chosen tab's panel as any selected tab does (TBV-2, TBV-6), its switch and the indicator's settling running over equal times.
- One declaration makes the tab and its view: while the Tab view draws its own row, each panel carries its tab's label, icon, labelHidden and disabled, so the tabs and the panels cannot fall out of step. Its row is a Tabs, not a look-alike: the same tokens, the same variants and the same behaviours, so a system that restyles the tabs restyles the Tab view's row too.
- Linked, for tabs placed elsewhere: a product whose tabs sit in a top app bar or a sticky header, apart from their content, sets tabs to name that row, and the Tab view draws none. The linked row names the panels, and its own props decide its look. The Tab view must then follow its tabs in reading order, so that Tab moves from the selected tab into the panel; a Tab view with its own row meets that by construction.
- Selection belongs to the Tab view and its tabs together: they hold the same value, and each selection is announced once. With its own row, the Tab view announces every selection (TBV-18). Linked, the component the person used announces it: the linked tabs' change for a tab, the Tab view's change for a swipe; a product that keeps the selection itself updates it from both.
- The panels' role, name and association are the Tab view's; the tabs' own panels (TAB-13) are for a row without a Tab view. Web: `tanrei-tab-panel` children of `tanrei-tab-view` get role tabpanel, aria-labelledby and the tab's aria-controls, and the view's own row is in the same tree as its panels so that these references resolve. SwiftUI, Compose, Flutter and React Native have no association between a tab and its panel; there each panel is named by its tab (Flutter exposes its tab panel role) and follows the row in reading order, and each README says so.
- A panel that is not shown stays in place, hidden, with its state. A product whose panels are costly renders their content when their tab is first selected; the Tab view has no lazy mode of its own yet (it can be added as a prop without breaking this spec).
- A tab of the Tab view's own row carries what a tab of the Tabs carries, and nothing more: the Tabs have no badge, so neither does a panel. A product that marks a tab with a badge writes its row itself and links it; a badge on the Tabs would reach the panel as one more tab prop.
- Tab view is not a carousel and not a pager: it shows one panel at a time, one per tab, and a person moves one panel per swipe. A row of items to browse belongs to the carousel.
- How a switch looks is a value decision of the active design system (tokens.md): a short travel with a fade and a blur by default, a full-width page turn in a system that pages.
- A Tab view in a group (group) is the usual home of a shared choice: the same sample in several languages or platforms, many times on one page. Its own row is the member (TBV-16), so everything the Tabs say of a group holds: the choice is matched by value, the views that follow change silently and at once and keep their hidden panels' state (TBV-20), the row the person used stays where it is on the screen (TAB-40, TBV-21), and remembering is off unless the product names a key (TAB-39).
- Panels of unequal height are the normal case for samples, and the view is as tall as its shown panel (TBV-11). What follows a view therefore moves when its panel changes; what a person is using does not. A product that wants no movement at all below a view sizes the view itself, and the panels then scroll inside it (TBV-11).
- The effect of switching views has two parts with two owners: the panels' change is the Tab view's (transition: slide, fade or none; TBV-22), and the indicator's travel to the new tab is the row's (the tabs' transition: slide or none; TAB-43). Set on a Tab view that draws its row, none covers both (TBV-23), so that "no effect" means that nothing moves; fade and slide leave the row to the system. A row placed by the product, linked or alone, is set by its own prop.
- transition changes what is drawn and never what is told: the selection, the panel's exposure and the announcement are made in the same frame in every form (TBV-9, TBV-22). None is the calm choice for panels that are read rather than watched — a code sample in several languages, a table in several units — and for a page that holds many views sharing one choice.
- Under fade the panels finish (comp.tab-view.motion.fade-duration) before the row's indicator lands (comp.tabs.motion.indicator-duration), as they do where motion is reduced; the rule that the switch and the indicator land together holds for slide.
- A swipe under none still switches panels and still shows the finger its progress, on the row's indicator; the panels wait for the release. A product that wants no swipe sets swipeable to false, which is a separate decision.
生成元 specs/tab-view.spec.json, tokens/sys/measure.tokens.json
- ビュー自身の並びは、ビューの
labelを名前とするタブリストで、各タブはパネルのlabelを名前とします。ビュー自体はロールを持ちません。 - 表示中のパネルはタブパネルとして伝え、自分の並びでもつないだ並びでも、タブを名前とし、タブと関連づけます。ほかのパネルは、切り替えやスワイプの途中でまだ描かれているものも含めて、支援技術からもフォーカスからも隠します。伝える対象は、動きが終わったときではなく、選択が変わったときに移ります。
- Tab を押すと、選択中のタブから表示中のパネルへ進みます。パネルの中の最初のフォーカスできる要素へ、何もなければパネル自体へ移ります。そのようなパネルは、キーボードフォーカスのときだけ、端の内側にフォーカスリングを表示します。
- フォーカスのあるパネルが切り替えで隠れると、フォーカスは選択中のタブへ移ります。
- ビューが独自に加えるキーはありません。タブの上の矢印キー、Home、End で別のタブを選ぶと、ビューが切り替わります。パネルの中では、矢印キーはその内容のものです。
- スワイプが唯一の手段になることはありません。どのパネルにも、タブのタップと矢印キーで届きます。スクリーンリーダーの動作中は、1 本指のスワイプはスクリーンリーダーのもので、切り替えにはタブを使います。
- スワイプは、指を離すまで何も読み上げません。取り消されたスワイプは何も変えません。
- グループに従うビューは静かに変わります。何も読み上げず、フォーカスは元の場所に残ります。変更で隠れるパネルにフォーカスがあった場合は、そのビューの選択中のタブへ移ります。
- 右から左(RTL)のレイアウトでは、スワイプと移動の向きが読む方向に合わせて反転します。パネルの内容をビューが反転することはありません。
- どの並びも指していない
tabs、パネルのない選択中のタブ、自分の並びを持ちながらパネルのないビュー、値が空または重複したパネル、自分のパネルを持つつないだ並び、つないだビューに設定された並びのプロパティは、デバッグビルドで報告します。
プラットフォームごとの既知の制約:
- SwiftUI、Compose、Flutter、React Native は、タブビューをまだ実装していません。
- Web:すべてのパネルがスクロールコンテナなので、パネルの端ぎりぎりにあるコントロールのフォーカスリングが切られます。
- Web:振る舞いのテストは Chromium でしか実行しておらず、スワイプは合成したポインターイベントでしか確かめていません。
- すべてのパネルに
labelを付けます。1 つの画面にタブの並びが複数あるときは、ビューにもlabelを付けます。 - つないだビューは、読み上げ順でタブの後ろに置きます。
- つなぐ形では、タブとビューの両方の
changeから自分の状態を更新します。 - パネル自体を横にドラッグする場合は、
swipeableを false にします。 - 同じグループのビューでは、同じ値に同じラベルを付けます。ビューどうしが離れているときは、最初のビューの近くで、選択がページ全体に適用されることを伝えます。
rememberAsのキーは利用者側で決め、必要な場合はプライバシーに関する説明に記載します。