ナビゲーションバー
コンパクトな画面の下端から、3〜5 個の最上位の行き先の間を移動し、今どこにいるかを示します。
- カテゴリ
- ナビゲーション
- トークン
comp.navigation-bar- レベル
- 基礎
- Web
tanrei-navigation-barschedule - SwiftUI
TanreiNavigationBarschedule - Jetpack Compose
TanreiNavigationBarschedule - Flutter
TanreiNavigationBarschedule - React Native
NavigationBarschedule
- 提供しないバリアント
- cupertino: filled · m3-expressive-like: filled · m3-like: filled
生成元 docs/status/status.json, specs/navigation-bar.spec.json
仕様は改訂済み、各プラットフォームは追従待ち
このページは、仕様の現在の形のナビゲーションバーを説明しています。タブにならった形です。アクティブインジケーターは、バーの幅を等分した今の行き先の持ち分全体で、バーの端から少し内側に収めて描きます。アイコンとラベルにぴったり沿うピルではありません。選択が変わると、その大きさのまま新しい行き先へスライドし、バーに沿ってドラッグすると、ポインターについて動きます。ホバー、押下、フォーカスも同じ箱の中に描きます。variant で Tonal(既定)と Filled を選べます。ほかのプロパティとイベントは、どちらの版でも同じです。上で追従待ち(lagging)と示したプラットフォームは、追いつくまで最初の版を提供します。variant もドラッグもなく、インジケーターがアイコンの後ろだけにあり、中央から広がって現れる形で、これらの例もその版のものです。このページのデモは、現在提供している Web コンポーネントです。
4 つの行き先。1 つに件数が付いています
Filled のバリアント:バーを押したまま横にドラッグすると、インジケーターがついて動きます
リンクのバー:ページが自分のアドレスで今の行き先を伝え、「ヘルプ」は新しいタブで開く
ナビゲーションバーが持つ項目の種類は destination(行き先)の 1 つだけです。アイコン、その下のラベル、アイコンに付けられるバッジでできています。行き先の渡し方は、各プラットフォームで繰り返しの子要素を渡すときの流儀に従います。Web では子要素の <tanrei-navigation-bar-item>、SwiftUI ではビュービルダーの中の TanreiNavigationBarItem、Compose ではコンテンツラムダの中の TanreiNavigationBarItem の呼び出し、Flutter では destinations: に並べた TanreiNavigationDestination、React Native ではプレーンなオブジェクトの配列 destinations です。
選択は、行き先の値によってバーが持ちます。value が今の行き先を指し、change が選ばれた行き先を伝えます。今の行き先をもう一度選ぶと reselect が発火するので、その画面を一番上までスクロールし直すといった対応ができます。値が空なら何も選ばれません。バーの行き先に含まれない画面を表示しているときに使います。SwiftUI では新しい値が selection のバインディングに書き込まれます。2 つのプラットフォームは、それぞれ自前のナビゲーションバーと同じ流儀を保ちます。Compose にはバー全体の値がなく、今の項目に利用者側で selected を付け、その onClick が change と reselect の両方を兼ねます。Flutter は番号で選びます。selectedIndex と onDestinationSelected で、今の行き先が選ばれたときにも呼ばれます。
variant は、今の行き先をどれだけ強く示すかを決めます。tonal(既定)はインジケーターを控えめに塗り、filled は強い色で塗って、その上のアイコンとラベルを反転させます。今のところ Web で使えます。
href を持つ行き先はリンクです。プラットフォーム自身のリンクで、ボタンの行き先とまったく同じに描かれます。Enter で実行でき、Space では実行しません。バーは、押されてもリンクを選択しません。行き先の値と href を持つ change を発火し、そのあとでリンクがたどられます。ナビゲーションドロワーと同じく、移った先のページが value で今の行き先を伝えます。value のないリンクは、href を自分の値とします。target と targetLabel はボタンのものと同じです。リンクの行き先があるのは Web です。SwiftUI、Compose、Flutter、React Native にはまだありません。
行き先の icon は、デザインシステムのアイコンフォントにあるグリフの名前です。選ばれていない間はアウトライン、選ばれている間は塗りで描かれます。badge、badgeCount、badgeLabel を指定すると、アイコンの上に Tanrei のバッジを描きます。件数がなければ点、あればその数です。SwiftUI、Compose、Flutter では 1 つの TanreiNavigationBadge の値として、React Native では badge オブジェクトとして渡します。
バーはレイアウト領域で、オーバーレイではありません。色は下にあるホームインジケーターやジェスチャーバーの下まで広がり、行き先はそれらより上、左右のインセットの内側に収まります。React Native では、インセットはセーフエリアのライブラリに頼らず、プロパティからだけ受け取ります。TanreiProvider の insets か、バー自身の insets で、後者は指定した辺だけ provider の値を置き換えます。
<!-- ホスト要素は、label を名前とするナビゲーションのランドマークです。 -->
<!-- variant="filled" は今の行き先を反転させます。にぎやかな画面の上のバーに使います。既定は Tonal です。 -->
<tanrei-navigation-bar label="メイン" value="inbox">
<tanrei-navigation-bar-item value="inbox" label="受信" icon="inbox"
badge badge-count="3" badge-label="未読 3 件"></tanrei-navigation-bar-item>
<tanrei-navigation-bar-item value="chat" label="チャット" icon="chat"
badge badge-label="新着メッセージ"></tanrei-navigation-bar-item>
<tanrei-navigation-bar-item value="starred" label="スター" icon="star"></tanrei-navigation-bar-item>
<tanrei-navigation-bar-item value="settings" label="設定" icon="settings" disabled></tanrei-navigation-bar-item>
</tanrei-navigation-bar>
<script type="module">
import '@tanrei/web/components/navigation-bar';
const bar = document.querySelector('tanrei-navigation-bar');
// バーは change を発火する前に自分の value を更新します。change もその値を伝えます。
bar.addEventListener('change', (e) => showScreen(e.detail.value));
bar.addEventListener('reselect', () => scrollTo({ top: 0 }));
</script>
<!-- リンクのバー:各ページが、自分のアドレスを value に指定します。value のないリンクは href を値とします。 -->
<tanrei-navigation-bar label="メイン" value="/search/">
<tanrei-navigation-bar-item href="/" label="ホーム" icon="home"></tanrei-navigation-bar-item>
<tanrei-navigation-bar-item href="/search/" label="検索" icon="search"></tanrei-navigation-bar-item>
<tanrei-navigation-bar-item href="/library/" label="ライブラリ" icon="video_library"></tanrei-navigation-bar-item>
<tanrei-navigation-bar-item href="https://example.com/help" label="ヘルプ" icon="help"
target="new" target-label="新しいタブで開きます"></tanrei-navigation-bar-item>
</tanrei-navigation-bar>
<script type="module">
// ルーターが移動を扱う場合:change をキャンセルし、detail.href へ移動して、プロダクトが今どこにいるかを伝えます。
bar.addEventListener('change', (e) => {
if (!e.detail.href) return;
e.preventDefault();
router.push(e.detail.href);
bar.value = e.detail.value;
});
</script>import { NavigationBar, NavigationBarItem } from '@tanrei/react';
// にぎやかな画面の上のバーには variant="filled"。既定は Tonal です。
<NavigationBar label="メイン" value={screen} onChange={(e) => setScreen(e.detail.value)} onReselect={scrollToTop}>
<NavigationBarItem value="inbox" label="受信" icon="inbox" badge badgeCount={3} badgeLabel="未読 3 件" />
<NavigationBarItem value="chat" label="チャット" icon="chat" />
<NavigationBarItem value="starred" label="スター" icon="star" />
</NavigationBar>import Tanrei
// 下側のセーフエリアのインセットに置く、専用のバーです。行き先を選ぶとバインディングに書き込まれます(change)。
ScreenView(screen)
.safeAreaInset(edge: .bottom, spacing: 0) {
TanreiNavigationBar(selection: $screen, accessibilityLabel: "メイン", onReselect: { _ in scrollToTop() }) {
TanreiNavigationBarItem("受信", icon: "inbox", value: "inbox",
badge: TanreiNavigationBadge(count: 3, label: "未読 3 件"))
TanreiNavigationBarItem("チャット", icon: "chat", value: "chat")
TanreiNavigationBarItem("スター", icon: "star", value: "starred")
// 無効にするのは SwiftUI 自身の disabled モディファイアーです。
TanreiNavigationBarItem("設定", icon: "settings", value: "settings").disabled(true)
}
}import io.github.chnotchy.tanrei.components.TanreiNavigationBadge
import io.github.chnotchy.tanrei.components.TanreiNavigationBar
import io.github.chnotchy.tanrei.components.TanreiNavigationBarItem
// Material の NavigationBar と同じく、バー全体の値はありません。今の項目に selected を付け、
// 1 つの onClick が change と reselect を兼ねます。
var screen by rememberSaveable { mutableStateOf("inbox") }
TanreiNavigationBar(contentDescription = "メイン") {
TanreiNavigationBarItem(
selected = screen == "inbox",
onClick = { if (screen == "inbox") { scrollToTop() } else { screen = "inbox" } },
icon = "inbox",
label = "受信",
badge = TanreiNavigationBadge(count = 3, contentDescription = "未読 3 件"),
)
TanreiNavigationBarItem(selected = screen == "chat", onClick = { screen = "chat" }, icon = "chat", label = "チャット")
// enabled は仕様の disabled を反転したものです。
TanreiNavigationBarItem(selected = screen == "settings", onClick = { screen = "settings" }, icon = "settings", label = "設定", enabled = false)
}import 'package:tanrei/tanrei.dart';
// NavigationBar と同じく、行き先の値はその番号です。バー自身は選択を持ちません。
TanreiNavigationBar(
selectedIndex: current, // null なら何も選びません
onDestinationSelected: (int index) {
if (index == current) {
scrollToTop(); // reselect
} else {
setState(() => current = index); // change
}
},
semanticLabel: 'メイン',
destinations: const <TanreiNavigationDestination>[
TanreiNavigationDestination(icon: 'inbox', label: '受信', badge: TanreiNavigationBadge(count: 3, semanticLabel: '未読 3 件')),
TanreiNavigationDestination(icon: 'chat', label: 'チャット'),
TanreiNavigationDestination(icon: 'settings', label: '設定', enabled: false),
],
),import { useState } from 'react';
import { NavigationBar } from '@tanrei/react-native';
const [screen, setScreen] = useState('inbox');
// 制御コンポーネントです。onValueChange を受けて value を更新します。
<NavigationBar
accessibilityLabel="メイン"
value={screen}
onValueChange={setScreen}
onReselect={scrollToTop}
destinations={[
{ value: 'inbox', label: '受信', icon: 'inbox', badge: { count: 3, label: '未読 3 件' } },
{ value: 'chat', label: 'チャット', icon: 'chat', badge: { label: '新着メッセージ' } },
{ value: 'settings', label: '設定', icon: 'settings', disabled: true },
]}
/>選択中のラベルだけを描くバーと、ラベルを描かないバー
構成要素
プロパティ
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
value | string | "" | The value of the selected destination (selection is the bar's, by a destination's value). Empty selects none (the current screen is not one of the bar's destinations). |
variant | tonal | filled | "tonal" | How the active indicator is filled. Tonal: a soft container with the content in that container's own color. Filled: the strong color, with the content inverted on it, for a bar that must stand out from a busy screen. Tonal destinations are drawn from the unselected and selected conditions, filled ones from filled-unselected and filled-selected. The indicator's size and place are the same in both. |
labelVisibility | always | selected | never | "always" | Which destinations draw their label: all of them, only the selected one, or none. A label that is not drawn is still the destination's accessible name, and no item container changes size with it. |
label | string | "" | Names the bar itself for assistive technology ("Main"); on the web it names the navigation landmark. |
イベント
| イベント | 説明 |
|---|---|
change | The user activated a destination that is not the selected one, or released a drag of the active indicator on one; carries its value and, for a link, its href. For a link it comes before the link is followed, only for an activation that follows it in place (NVB-31), and a product that cancels it follows the link itself. |
reselect | The user activated the destination that is already selected (a product may scroll that screen to its top); carries its value and, for a link, its href, as change does. A drag that returns the indicator to it does not emit it. |
アイテム
このコンポーネントが繰り返すアイテムの種類です。フィクスチャはアイテムを順に並べ、各プラットフォームはそれぞれの流儀で種類ごとに名前を付けます。
destination
One top-level destination of the bar: an icon, a label under the icon, and optionally a badge on the icon.
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
value | string | "" | What identifies the destination. The bar's value selects the destination whose value equals it; change and reselect carry it. Unique within the bar. Empty on a link, its href stands as its value. |
href | string | "" | The address the destination leads to. Not empty, the destination is a link and not a button (NVB-30): it is followed as the platform follows a link, and the bar does not select it by itself (NVB-31). Empty, the destination is a button and the product moves on change. As the Button's href (a control with a destination is a link). |
target | same | new | "same" | Where a link opens, as the Button's target. same: where the person is. new: a new context — a new tab or window on the web, outside the current screen elsewhere — which a bar's cell has no place to mark and says through targetLabel (NVB-32). A link that opens elsewhere is never selected by a press (NVB-31). Ignored without href. |
targetLabel | string | "" | What assistive technology reads after the link's name while target is new ("opens in a new tab"), as the Button's targetLabel: Tanrei ships no words of its own. Required while target is new; ignored otherwise. |
label | string | "" | Its name, drawn under the icon (see the bar's labelVisibility) and always its accessible name. Required. |
icon | string | "" | The name of its glyph in the system's icon set, drawn in the destination's own icon style: outlined while unselected, filled while selected. Required. |
disabled | boolean | false | Makes it unavailable. It keeps its place, draws its disabled look, cannot be activated and leaves the focus order. |
badge | boolean | false | Draws a Tanrei badge (the badge component) on the icon, from badgeCount and badgeLabel. |
badgeCount | number | null | The badge's count, as the badge component's count: null draws the dot, 0 draws nothing, above 0 the number (above 999 as 999+). Ignored while badge is false. |
badgeLabel | string | "" | The badge's label, as the badge component's: what assistive technology reads after the destination's name and state ("3 unread"). A dot needs one. Ignored while badge is false. |
| 仕様 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| アイテム | tanrei-navigation-bar-item | TanreiNavigationBarItem | TanreiNavigationBarItem | TanreiNavigationDestination | NavigationBarDestination |
value · プロパティ | value | value | selected | destinations | value |
href · プロパティ | href | destination | href | href | href |
target · プロパティ | target | target | target | target | target |
targetLabel · プロパティ | targetLabel | targetLabel | targetLabel | targetSemanticLabel | targetLabel |
label · プロパティ | label | title | label | label | label |
icon · プロパティ | icon | icon | icon | icon | icon |
disabled · プロパティ | disabled | disabled | enabled | enabled | disabled |
badge · プロパティ | badge | badge | badge | badge | badge |
badgeCount · プロパティ | badgeCount | count | count | count | count |
badgeLabel · プロパティ | badgeLabel | label | contentDescription | semanticLabel | label |
| 備考 | icon is the glyph's name, as <tanrei-tab>'s. badgeCount and badgeLabel are the badge-count and badge-label attributes; the element draws a <tanrei-badge> around the glyph from them. href is the spec's own prop: the cell is an <a href> in the item's shadow root. target is the target attribute with the values same and new (the HTML spellings _self and _blank are read as those two), targetLabel the target-label attribute, and rel (to which new adds noopener) and download are web-only and forwarded to the anchor, all as <tanrei-button>'s. Space is not handled on it. With no value attribute the item's value property reads its href attribute as written (not the resolved URL). | TanreiNavigationBarItem(_ title: String, icon: String, value: Value, badge: TanreiNavigationBadge? = nil), where icon is the glyph's name and TanreiNavigationBadge(count: Int? = nil, label: String = "") is drawn with .tanreiBadge(…) on the glyph (no badge is nil). disabled follows .disabled(_:) on the item. destination: URL? = nil makes it a link (the link trait beside the item's selected trait), opened by the environment's openURL after the selection callback, which an app that routes addresses itself answers with its own OpenURLAction; the binding is not written for it. target: TanreiLinkTarget = .same and targetLabel (the accessibility hint), as the Button's. | TanreiNavigationBarItem(selected, onClick, icon = "inbox", label = "…", enabled = true, badge = null) in the bar's RowScope content lambda. enabled is the inverse flag. badge: TanreiNavigationBadge? with TanreiNavigationBadge(count: Int? = null, contentDescription: String = ""); the item draws a TanreiBadge around its icon from it. icon is the glyph's name; the item draws it in the selection's icon style. href: String? = null makes it a link: opened through LocalUriHandler.current after onClick (an app that routes addresses itself provides its own UriHandler); the item keeps Role.Tab, which Compose cannot join with a link role. target: TanreiLinkTarget = TanreiLinkTarget.Same, and targetLabel is appended to the state description, as the Button's. | TanreiNavigationDestination(icon:, label:, enabled: true, badge: null) in the bar's destinations: list; its value is its index there. icon is the glyph's name. badge: TanreiNavigationBadge(count:, semanticLabel:), drawn as a TanreiBadge around the icon. enabled: false disables it. href: Uri? = null makes it a link (Semantics(link: true) with the address as its linkUrl, beside the tab semantics), opened by the nearest TanreiLinkOpener after onDestinationSelected; target is a TanreiLinkTarget and targetSemanticLabel the semantics hint, as the Button's. | An entry of destinations: NavigationBarDestination[]: { value, label, icon, disabled?, badge? }, where icon is the glyph's name and badge?: { count?: number | null; label?: string } (absent is no badge) is drawn as a Badge around the icon. href?: string makes it a link: the destination keeps the tab role (React Native gives an element one role), onValueChange receives the value and the href, and Linking.openURL(href) follows it unless the product handles it; target is "same" | "new" and targetLabel the accessibilityHint, as the Button's. |
状態
振る舞い
EARS 記法による要件です。各プラットフォームの実装は、この ID に対応するテストで検証されます。要件の原文は仕様ファイルのものをそのまま掲載しています。
| ID | 要件 |
|---|---|
NVB-1 | WHEN the user activates a destination by pointer, touch, Enter or Space (Enter only for a destination that is a link, and for a link only an activation that NVB-31 counts) THE navigation bar SHALL emit change exactly once with the destination's value WHILE it is not the selected destination, and reselect exactly once with its value WHILE it is. |
NVB-2 | WHILE a destination's disabled is true THE navigation bar SHALL NOT emit change or reselect for it, SHALL NOT make it focusable and SHALL expose it as disabled to assistive technology. |
NVB-3 | THE navigation bar SHALL draw the destination whose value equals the bar's value from its variant's selected condition and every other destination from its variant's unselected condition: WHILE variant is tonal the selected condition is selected (comp.navigation-bar.selected.item.container.color, comp.navigation-bar.selected.item.icon-indicator.color, comp.navigation-bar.selected.item.icon.color, comp.navigation-bar.selected.item.label.color, comp.navigation-bar.selected.item.covered-icon.color, comp.navigation-bar.selected.item.covered-label.color) and the unselected condition is unselected; WHILE variant is filled they are filled-selected (comp.navigation-bar.filled-selected.item.container.color and the same parts) and filled-unselected. IF no destination's value equals a value that is not empty THE navigation bar SHALL draw every destination from its variant's unselected condition, and in debug builds report the value. |
NVB-4 | WHILE a destination is hovered, keyboard-focused or pressed THE navigation bar SHALL render its item container, icon indicator, icon, label, covered icon and label, and opacity from the component tokens of its condition in the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state. |
NVB-5 | WHEN a destination receives keyboard focus THE navigation bar SHALL show the focus indicator (the ring or the glow, NVB-10) around its item container (NVB-12), concentric with it and in its shape, with its inner edge sys.focus.offset outside the container's edge, and SHALL hold it inside the bar: WHERE the space between the item container and the bar's edges (comp.navigation-bar.container.padding) is less than the indicator reaches, it SHALL be drawn further in by the difference, on every side alike, keeping the container's shape. THE indicator SHALL be drawn above the neighbouring destinations and the active indicator, without animation; focus gained by pointer or touch SHALL NOT show it. |
NVB-6 | THE navigation bar SHALL make each destination's hit area its whole cell, the bar's full height above the system inset and its share of the bar's width (NVB-11), the first and the last cell reaching the bar's start and end edges, and SHALL NOT let a cell's hit area fall below sys.target.touch in either direction; hover and press over any part of the cell SHALL answer on the item container. WHERE an equal share of the bar's width is narrower than sys.target.touch (more destinations than the width holds), the cells SHALL still share the bar's width and narrow below the target rather than make the bar wider than the space it is given. |
NVB-7 | WHILE a destination's disabled is true THE navigation bar SHALL render its item container, icon indicator, icon, label, covered icon and label, and opacity from the disabled component tokens of its condition. |
NVB-8 | WHEN a destination is pressed by pointer, touch, Enter or Space THE navigation bar SHALL show its pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner. |
NVB-9 | WHILE a destination is pressed THE navigation bar SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole item container at once; ripple grows the pressed state layer, clipped to the item container, from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole destination and draws no state layer; none draws no answer to the press. Under ripple and dim only the item container color stays with the state under the press; icon indicator, icon, label, covered icon and label, and opacity still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.navigation-bar.motion.feedback-duration with comp.navigation-bar.motion.state-easing, fades over comp.navigation-bar.motion.state-duration with comp.navigation-bar.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced. |
NVB-10 | THE navigation bar SHALL ease its item container and icon indicator 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. |
NVB-11 | THE navigation bar SHALL span the full width of its parent, at least comp.navigation-bar.container.height high above the system inset, filled with comp.navigation-bar.container.color, raised by comp.navigation-bar.container.elevation and edged at the top by comp.navigation-bar.outline.width of comp.navigation-bar.outline.color, and SHALL divide its width, less comp.navigation-bar.container.padding at its start and at its end, into equal cells, one per destination, in reading order from the start edge; right-to-left layouts SHALL mirror the order. THE bar's container SHALL be the same whichever destination is selected, whatever state a destination is in and while the active indicator moves: only the active indicator marks the selection. |
NVB-12 | THE navigation bar SHALL draw each destination's item container as wide as its cell and as tall as the bar above the system inset less comp.navigation-bar.container.padding above and below, the same size for every destination of the bar whatever each holds, with its four corners rounded by comp.navigation-bar.item.container.shape, each at most half its height, and SHALL NOT size it to the destination's content. Inside it THE navigation bar SHALL lay out the destination as a column centered in the item container: the icon indicator, comp.navigation-bar.item.icon-indicator.width wide and comp.navigation-bar.item.icon-indicator.height high with corners comp.navigation-bar.item.icon-indicator.shape, its icon centered in it; and WHILE the label is drawn, the label below it, comp.navigation-bar.item.gap away, centered, on one line in comp.navigation-bar.item.label.typescale. THE column SHALL keep comp.navigation-bar.item.container.padding-block between itself and the item container's top and bottom edges, the bar growing in height to keep it (NVB-21), and a label wider than the item container less comp.navigation-bar.item.container.padding-inline on each side SHALL be truncated at its end with an ellipsis. THE navigation bar SHALL NOT draw hover, focus or press on the part of the cell outside the item container. Every part is laid out in every system; a transparent color hides a part without changing the layout. |
NVB-13 | WHILE labelVisibility is always THE navigation bar SHALL draw every destination's label; WHILE it is selected it SHALL draw only the selected destination's label; WHILE it is never it SHALL draw none. A destination that draws no label SHALL center its icon indicator in its item container, whose size does not change with the label, and every destination's label SHALL remain its accessible name. |
NVB-14 | THE navigation bar SHALL draw each icon at comp.navigation-bar.item.icon.size with the family and fill of its condition (selected for the selected destination, unselected for every other), sys.icon.weight, and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max. |
NVB-15 | WHEN the selected destination changes while no drag is followed (NVB-27) THE navigation bar SHALL slide the active indicator (the fill of the selected condition's item container) from the previous destination's item container to the new one's, at its full size throughout and drawn in the new destination's colors, every frame of the slide being the in-between look of NVB-27 (the indicator as a drag would draw it at that place, each icon and label in its covered color exactly where it is covered); SHALL grow the new destination's icon indicator in width from its center to its full width, at its full height throughout, and fade the previous one out; and SHALL change icon and label colors and icon fill; all over comp.navigation-bar.motion.selection-duration with comp.navigation-bar.motion.selection-easing. WHERE no destination was selected before, the active indicator SHALL fade in on the new item container at its full size, and WHERE none is selected after, it SHALL fade out, over the same time; it SHALL NOT grow or shrink in width to appear or disappear. WHILE labelVisibility is selected the labels SHALL appear and disappear over the same time, without resizing or moving any item container. WHERE motion is reduced it SHALL show the new selection at once. WHEN the bar first appears, or the selected destination changes size or place without a new selection (a font arriving, the text size changing, the bar resizing), THE navigation bar SHALL put the active indicator on it at once, without a slide. |
NVB-16 | WHILE a destination's badge is true THE navigation bar SHALL draw a Tanrei badge anchored to its icon as the badge component draws one whose count is the destination's badgeCount and whose label is its badgeLabel, SHALL keep it while the destination is disabled, and SHALL expose the badge's description (badgeLabel, or the displayed count when it is empty) as part of the destination's own description, after its name and state. |
NVB-17 | IF the navigation bar holds fewer than three or more than five destinations THE navigation bar SHALL still render them, and in debug builds report the count. |
NVB-18 | THE navigation bar SHALL extend its container under the bottom system inset (home indicator, gesture bar, taskbar) and SHALL place its destinations above that inset and inside the left and right insets. |
NVB-19 | THE navigation bar SHALL expose each destination as one element named by its label, with its selected state and its position in the set (for example 2 of 4), and its icon hidden: a link WHILE its href is not empty (NVB-30) and a button otherwise. On the web it is an anchor or a button inside a navigation landmark named by the bar's label, the selected destination carrying aria-current="page"; on Apple platforms, Android, Flutter and React Native a tab of a tab bar, with the selected state, a link destination included: there the tab is what carries the position and the selection a bar is read by, and a link destination adds the link trait to it where the platform lets one element carry both. The selected destination is the place the product is showing, in a product of one screen and in a site of many pages alike: THE navigation bar SHALL have no second state and no second mark for a current page, and SHALL expose exactly one destination, or none, as current. |
NVB-20 | WHILE a destination's label is not empty THE navigation bar SHALL expose it as the destination's accessible name, over a name the product gives the destination on the element (aria-label or aria-labelledby); WHILE a destination's label is empty and the product gives the destination a name of its own on the element — aria-label, or aria-labelledby naming an element — THE navigation bar SHALL expose that name as its accessible name; WHILE a destination's label is empty and the product gives it none THE navigation bar SHALL still render it, and in debug builds report that it has no accessible name. |
NVB-21 | WHILE the text size is enlarged THE navigation bar SHALL grow its height to hold its labels on one line inside their item containers (NVB-12), every item container growing with the bar alike, and SHALL NOT scale its icon indicators or icons; WHERE the platform offers a large-content viewer for bar items (iOS) a long press SHALL show the destination's icon and label enlarged. |
NVB-22 | IF two destinations have the same value THE navigation bar SHALL treat the first of them as the one the bar's value selects, and in debug builds report the repeated value. |
NVB-23 | THE navigation bar SHALL draw each destination's icon and label a second time, over the first, in the covered colors of its condition (comp.navigation-bar.unselected.item.covered-icon.color and comp.navigation-bar.unselected.item.covered-label.color for a tonal destination that is not selected, comp.navigation-bar.selected.item.covered-icon.color and comp.navigation-bar.selected.item.covered-label.color for the tonal selected one, comp.navigation-bar.filled-unselected.item.covered-icon.color, comp.navigation-bar.filled-unselected.item.covered-label.color, comp.navigation-bar.filled-selected.item.covered-icon.color and comp.navigation-bar.filled-selected.item.covered-label.color for a filled bar), clipped to the active indicator's current shape, its corners included, wherever the indicator is drawn; WHILE the indicator slides (NVB-15) or follows a drag (NVB-27) the clip SHALL move with it in the same frame, so that a destination it passes over changes where it is covered and the newly selected destination's icon and label are wholly in their covered colors when it lands, neither before nor after. THE navigation bar SHALL NOT approximate the covered colors with a blend mode, SHALL draw the badge above both copies, and SHALL expose each destination to assistive technology once. |
NVB-24 | THE navigation bar 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 navigation bar is shown THE navigation bar SHALL draw itself for the new direction at once, without a motion of its own, keeping the selected destination and its focus; a drag (NVB-27) SHALL carry the active indicator to where the pointer is in either direction. |
NVB-25 | WHERE the layout direction is right-to-left THE navigation bar SHALL draw a destination's icon, in both of its copies (NVB-23) mirrored left to right exactly when the system's icon set marks that glyph's name as mirrored or the product forces mirroring for that use, and unmirrored when the product forbids it: the icon helper mirrors the glyph inside its own box, about the box's vertical centre line, changing neither its place, size, color and fill nor the order NVB-11 gives. THE navigation bar SHALL NOT mirror a glyph itself, and SHALL NOT mirror content the product gives as an image or a view of its own rather than by a glyph's name. |
NVB-26 | THE navigation bar SHALL lay out a destination's label, in both of its copies (NVB-23) as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE navigation bar SHALL still place each label and align its lines by the layout direction, not by the text's own, and SHALL cut a label that does not fit (NVB-12) at the end of its own direction, so that a left-to-right label in a right-to-left layout loses its right end and never its beginning. |
NVB-27 | WHEN the user presses the bar with touch, a pen or a mouse and drags past the platform's touch slop (8 points where the platform names none), further sideways than up or down, WHILE a destination is selected, THE navigation bar SHALL take the drag: the press SHALL activate no destination when it ends (NVB-1), the destination under it SHALL leave its pressed state as it does when a press ends (NVB-8), and from then until the drag ends THE navigation bar SHALL draw the active indicator where the pointer is along the bar — on an enabled destination's item container while the pointer is at that cell's center, halfway between two neighbouring enabled destinations while the pointer is where their cells meet (the middle of any disabled destinations between them), at the linear interpolation in between, and on the first or last enabled destination while the pointer is before or beyond its center — at its full size, in the selected destination's colors. Everything that differs between the indicator on the one destination and on the other SHALL change continuously with the pointer, with no threshold: its place, and the color of each icon and label, which is its covered color exactly where the indicator covers it and its own color elsewhere, the clip of NVB-23 moving with the indicator in the same frame. The icon indicator, each icon's fill and, WHILE labelVisibility is selected, the label that is drawn SHALL stay with the selected destination until the release (NVB-28). THE navigation bar SHALL place the indicator in the frame the pointer moves, without a motion of its own; WHEN the drag is taken with the pointer away from where the indicator rests THE navigation bar SHALL bring it to the pointer's place over comp.navigation-bar.motion.state-duration with comp.navigation-bar.motion.state-easing instead of at once, following the pointer meanwhile. A drag that is further up or down than sideways when it passes the slop SHALL be left to the page and to the system's gestures at the bottom edge for its whole length, a wheel or a trackpad SHALL move nothing, and WHILE no destination is selected THE navigation bar SHALL take no drag. |
NVB-28 | WHEN the user releases a drag the navigation bar has taken THE navigation bar SHALL choose one enabled destination: the one the active indicator is nearer to of the two it lies between (the one after it from halfway on), or, WHEN the release is a fling along the bar, the one of those two that the fling is toward; 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 navigation bar 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. WHERE the chosen destination is not the selected one and is not a link THE navigation bar SHALL activate it as NVB-1 does — emit change exactly once with its value — and SHALL slide the active indicator from where the release left it onto that destination over comp.navigation-bar.motion.selection-duration with comp.navigation-bar.motion.selection-easing, as NVB-15 does from a destination; WHERE the chosen destination is not the selected one and is a link THE navigation bar SHALL treat the release as a plain press on that link — emitting change and following the link as NVB-31 says, or only opening it where NVB-31 does not count it — SHALL NOT write value, and SHALL return the active indicator to the destination that value selects as NVB-29 returns it, the indicator going on to the chosen destination when the product sets value (NVB-15); WHERE it is the selected destination THE navigation bar SHALL return the indicator to it over the same motion and SHALL emit neither change nor reselect. The place where two cells meet decides only what a release chooses; nothing drawn changes at it. The release SHALL NOT also activate the destination under the pointer. |
NVB-29 | WHEN a drag the navigation bar has taken is cancelled — the platform cancels the pointer (a system gesture, a lost capture), Escape is pressed while it lasts, or the product sets value or removes or disables a destination the indicator lies between — THE navigation bar SHALL return the active indicator to the selected destination (the one the product has just set, where it set one) over comp.navigation-bar.motion.selection-duration with comp.navigation-bar.motion.selection-easing and emit nothing. WHILE a drag lasts THE navigation bar SHALL NOT change value, keyboard focus, or the selected state that each destination exposes to assistive technology, whatever the indicator covers, and SHALL announce nothing: the selection is made, and announced, by the release alone (NVB-28). WHERE motion is reduced THE navigation bar SHALL still draw the active indicator where the pointer is, which the person's own finger moves, taking the drag without the catch-up motion of NVB-27, and SHALL put it on its destination at once when the drag is released or cancelled. |
NVB-30 | WHILE a destination's href is not empty THE navigation bar SHALL make it a link to that address and not a button. On the web it SHALL be an anchor element with that address, whose behaviours are the browser's own — following it on a plain click, a touch or Enter; opening it in another tab or window by a modified click or a middle click; the link's context menu; copying its address; showing its address on hover — and THE navigation bar SHALL NOT prevent, replace or imitate any of them, with one exception: a sideways drag that begins on a link destination moves the active indicator (NVB-27) and does not drag the link's address, because one gesture cannot do both and the drag is the bar's own way of moving its selection; Space SHALL NOT activate a link. On other platforms it SHALL be the platform's own link (SwiftUI's Link; on Compose, Flutter and React Native an element with the platform's link semantics whose address is opened by the platform's opener), as BTN-22 says for a link button. THE navigation bar SHALL draw a link exactly as it draws a button destination, from the same tokens in every state and both variants (NVB-3, NVB-4, NVB-7 to NVB-9, NVB-12, NVB-23), SHALL draw no look of its own for a link the user has visited, and SHALL give its hit area (NVB-6) to the link. WHILE a link's value is empty THE navigation bar SHALL take its href as its value, for value, change, reselect and NVB-22. WHILE a link's disabled is true it SHALL keep the link role with the disabled state and no destination, and SHALL open nothing by any means the platform gives a link (NVB-2; on the web the anchor carries no address). |
NVB-31 | WHEN the user activates a link destination THE navigation bar SHALL count it as an activation (NVB-1) only WHILE it follows the link in the place where the product is shown: a click with the primary button and no modifier key, a touch, Enter, or the default action of assistive technology, on a link whose target is same and that asks for no download. It SHALL then emit change or reselect exactly once, carrying the destination's value and its href, before the link is followed, and SHALL NOT hold the link back for sys.motion.duration.press-hold; SHALL NOT write value itself on any platform, because where the product is, is the product's to say, and the page or the route shown next sets value; SHALL keep the active indicator on the destination that value selects, moving it as NVB-15 says only when value changes; and SHALL let the link be followed unless the product cancels the event (preventDefault on the web), as a product does that moves between its pages without loading a document. For every other activation of a link — a modified or a middle click, a link whose target is new, a download — THE navigation bar SHALL emit neither change nor reselect, and SHALL keep value and the drawn selection as they are. |
NVB-32 | WHILE a destination is a link and its target is new THE navigation bar SHALL open its href in a new context, as BTN-24 says for a link button (on the web a new tab or window that is given no handle to the page that opened it), and SHALL draw no mark for it: a destination's cell is an icon over a label and has no trailing glyph place for a mark to stand in, as an icon-only button has none, and a second glyph beside the icon would be read as its badge. It SHALL expose targetLabel as part of the destination's description, after its badge's description (NVB-16), and WHILE targetLabel is empty SHALL still render and in debug builds report that a link that opens a new context does not say so. WHILE target is same THE navigation bar SHALL ignore targetLabel. |
NVB-33 | WHEN a destination's target names a value the navigation bar does not define THE navigation bar SHALL treat it as same, and in debug builds report the unknown value. IF a destination's href is only white space, or names a script to run rather than a place to go (the web's javascript: scheme), or cannot be read as an address by the platform, THE navigation bar SHALL make the destination a button without a destination, and in the last two cases report it in debug builds, as BTN-26 says for a button. WHILE a destination's href is empty THE navigation bar SHALL ignore its target and targetLabel. |
NVB-34 | WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on a destination's host, by the attribute or by the ARIA property that reflects it, THE navigation bar 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 element that carries the destination's role (its link or its button) — an id reference by the elements it names in the host's tree or a tree around it, an error message as part of the description while the destination is invalid — except those a destination owns (aria-current, aria-disabled), which it SHALL move and not expose. 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 navigation bar SHALL take it off that element. WHEN the product sets an aria- attribute a destination owns THE navigation bar SHALL report it in debug builds. The bar's host carries its navigation landmark itself (NVB-19), so what the product writes on it is not moved, but for a name: WHILE the bar's label is not empty THE navigation bar SHALL hold the product's aria-label and aria-labelledby on that host as their data-aria- twins, so that label is the landmark's whole name although the platform gives aria-labelledby the precedence, and WHEN label is emptied SHALL give them back as they were written, so that a name the product gives names the landmark; WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it to null, THE navigation bar SHALL take it away. |
プラットフォーム別の名前対応
パリティは API が同一であることを意味しません。同じ契約を、各プラットフォームの流儀で表現します。
| 仕様 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| コンポーネント | tanrei-navigation-bar | TanreiNavigationBar | TanreiNavigationBar | TanreiNavigationBar | NavigationBar |
value · プロパティ | value | selection | selected | selectedIndex | value |
variant · プロパティ | variant | variant | variant | variant | variant |
labelVisibility · プロパティ | labelVisibility | labelVisibility | labelVisibility | labelBehavior | labelVisibility |
label · プロパティ | label | accessibilityLabel | contentDescription | semanticLabel | accessibilityLabel |
change · イベント | change | selection | onClick | onDestinationSelected | onValueChange |
reselect · イベント | reselect | onReselect | onClick | onDestinationSelected | onReselect |
| 備考 | Destinations are <tanrei-navigation-bar-item> children, in order. The bar selects the activated destination itself (sets value) before firing change, as a native control does, unless the destination is a link. For a link destination change and reselect are cancelable, carry detail.href beside detail.value, and are fired from the anchor's click only when event.button is 0, no modifier key is down, the click was not already cancelled, and the item's target is same and it has no download; cancelling one calls preventDefault() on that click. The bar does not set value for a link. variant is the variant attribute (tonal, filled) and labelVisibility the label-visibility attribute. The host is a navigation landmark (role="navigation", aria-label from label); the selected destination has aria-current="page". The active indicator is one layer of the bar that slides between the destinations' item containers; the covered copy of every icon and label (NVB-23) is an aria-hidden layer clipped with clip-path to the indicator's box, animated with it. A drag (NVB-27 to NVB-29) is followed with Pointer Events, as tanrei-tabs follows its own: pointerdown on the bar, setPointerCapture once the drag is taken, pointermove placing the indicator through the same custom properties a slide transitions, pointerup choosing the destination, and pointercancel, lostpointercapture or Escape cancelling; the click that follows a taken drag is swallowed, aria-current is written only on the release, a destination that is a link is not draggable as a link (draggable="false", the one behaviour of a link the bar takes, NVB-30) and is followed with its own click() when a release chooses it, the indicator then returning to the destination value selects (NVB-28), and touch-action: pan-y leaves vertical drags to the page. The bottom inset is env(safe-area-inset-bottom). It does not yet follow NVB-30 to NVB-33, nor the parts of NVB-1, NVB-19 and NVB-28 that concern links: a pressed link destination is selected like a button, change is not cancelable and carries no href, and the destination writes the link rules itself instead of using the shared link primitive (packages/web/src/primitives/link.ts). | TanreiNavigationBar(selection: Binding<Value>, variant:, labelVisibility:, onReselect:) { TanreiNavigationBarItem(…) … }, placed with .safeAreaInset(edge: .bottom). A bar of its own rather than a restyled TabView, whose bar the tokens cannot reach (indicator, geometry, colors). Activation writes the binding (change); activating the selected item calls onReselect. The bar is exposed as a tab bar and each item as a button with the selected trait; each item supports the large-content viewer. It lags the spec: it has no variant, and draws the active indicator behind the icon alone (the item container at its least size), growing in from its center instead of sliding, with the label outside it. Not built on this platform yet: the item container as wide as the destination's cell and held in by container.padding, hover, focus and press drawn in that box, and the drag of the active indicator (NVB-27 to NVB-29): a DragGesture(minimumDistance:) beside each item's tap, predictedEndTranslation for the fling, the binding written in onEnded. Link destinations are not built yet either (NVB-30 to NVB-33 and the parts of NVB-1, NVB-19 and NVB-28 that concern links, with the fixtures links, links-rtl and links-disabled). | Compose idiom (as NavigationBar): TanreiNavigationBar(variant, labelVisibility, contentDescription) { TanreiNavigationBarItem(…) … }. There is no bar-level value: the caller marks the selected item with selected = (current == value), so the bar's value and a destination's value both map to the item's selected, and one onClick per item covers change and reselect (the caller knows which item is selected). The bar draws the sliding active indicator behind its items, from the bounds of the selected item. Insets follow WindowInsets.navigationBars plus the horizontal display cutout, as a windowInsets parameter. Items have Role.Tab in a selectableGroup(). It lags the spec: it has no variant, and draws the active indicator behind the icon alone (the item container at its least size), growing in from its center instead of sliding, with the label outside it. Not built on this platform yet: the item container as wide as the destination's cell and held in by container.padding, hover, focus and press drawn in that box, and the drag of the active indicator (NVB-27 to NVB-29): pointerInput with detectHorizontalDragGestures on the bar and a VelocityTracker for the fling; the release calls the chosen item's onClick. Link destinations are not built yet either (NVB-30 to NVB-33 and the parts of NVB-1, NVB-19 and NVB-28 that concern links, with the fixtures links, links-rtl and links-disabled). | Flutter idiom (as NavigationBar): TanreiNavigationBar(selectedIndex:, onDestinationSelected:, variant:, labelBehavior:, destinations: [TanreiNavigationDestination(…), …]). A destination's value is its index in destinations; a selectedIndex of null selects none. onDestinationSelected is called for the selected destination too (reselect); the caller compares. variant is TanreiNavigationBarVariant.tonal or .filled; labelBehavior is alwaysShow, onlyShowSelected or alwaysHide. Bottom inset from MediaQuery.padding via SafeArea. It lags the spec: it has no variant, and draws the active indicator behind the icon alone (the item container at its least size), growing in from its center instead of sliding, with the label outside it. Not built on this platform yet: the item container as wide as the destination's cell and held in by container.padding, hover, focus and press drawn in that box, and the drag of the active indicator (NVB-27 to NVB-29): a GestureDetector's horizontal drag callbacks on the bar, DragEndDetails.primaryVelocity for the fling; the release calls onDestinationSelected. Link destinations are not built yet either (NVB-30 to NVB-33 and the parts of NVB-1, NVB-19 and NVB-28 that concern links, with the fixtures links, links-rtl and links-disabled). | Controlled, in React Native's value / onValueChange idiom, with a destinations data array; variant is 'tonal' or 'filled'. The bar has the tablist role and each destination the tab role with accessibilityState.selected; the covered copy is hidden with importantForAccessibility="no-hide-descendants" and accessibilityElementsHidden. Insets come from the bar's own insets prop, side by side, falling back to TanreiProvider's insets; the package takes no safe-area dependency, and a product that uses react-native-safe-area-context passes its useSafeAreaInsets() to either. It lags the spec: it has no variant, and draws the active indicator behind the icon alone (the item container at its least size), growing in from its center instead of sliding, with the label outside it. Not built on this platform yet: the item container as wide as the destination's cell and held in by container.padding, hover, focus and press drawn in that box, and the drag of the active indicator (NVB-27 to NVB-29): a PanResponder on the bar (or React Native Gesture Handler's Gesture.Pan() where the product has it), the release's vx for the fling; the release calls onValueChange. Link destinations are not built yet either (NVB-30 to NVB-33 and the parts of NVB-1, NVB-19 and NVB-28 that concern links, with the fixtures links, links-rtl and links-disabled). |
トークン
| トークン | 参照先 | 解決値(ライト) |
|---|---|---|
--tanrei-comp-navigation-bar-container-color | sys.color.surface | |
--tanrei-comp-navigation-bar-container-elevation | sys.elevation.level0 | |
--tanrei-comp-navigation-bar-container-height | — | 64px |
--tanrei-comp-navigation-bar-container-padding | sys.space.space50 | 4px |
--tanrei-comp-navigation-bar-disabled-opacity | — | 1 |
--tanrei-comp-navigation-bar-filled-selected-disabled-item-container-color | — | |
--tanrei-comp-navigation-bar-filled-selected-disabled-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-filled-selected-disabled-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-filled-selected-disabled-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-filled-selected-disabled-item-icon-color | — | |
--tanrei-comp-navigation-bar-filled-selected-disabled-item-label-color | — | |
--tanrei-comp-navigation-bar-filled-selected-disabled-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-filled-selected-focused-item-container-color | — | |
--tanrei-comp-navigation-bar-filled-selected-focused-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-filled-selected-focused-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-filled-selected-focused-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-filled-selected-focused-item-icon-color | — | |
--tanrei-comp-navigation-bar-filled-selected-focused-item-label-color | — | |
--tanrei-comp-navigation-bar-filled-selected-focused-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-filled-selected-hovered-item-container-color | — | |
--tanrei-comp-navigation-bar-filled-selected-hovered-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-filled-selected-hovered-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-filled-selected-hovered-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-filled-selected-hovered-item-icon-color | — | |
--tanrei-comp-navigation-bar-filled-selected-hovered-item-label-color | — | |
--tanrei-comp-navigation-bar-filled-selected-hovered-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-filled-selected-item-container-color | sys.color.primary | |
--tanrei-comp-navigation-bar-filled-selected-item-covered-icon-color | sys.color.on-primary | |
--tanrei-comp-navigation-bar-filled-selected-item-covered-label-color | sys.color.on-primary | |
--tanrei-comp-navigation-bar-filled-selected-item-icon-indicator-color | comp.navigation-bar.selected.item.icon-indicator.color | |
--tanrei-comp-navigation-bar-filled-selected-item-icon-color | comp.navigation-bar.selected.item.icon.color | |
--tanrei-comp-navigation-bar-filled-selected-item-label-color | comp.navigation-bar.selected.item.label.color | |
--tanrei-comp-navigation-bar-filled-selected-item-state-layer-color | comp.navigation-bar.filled-selected.item.covered-icon.color | |
--tanrei-comp-navigation-bar-filled-selected-pressed-item-container-color | — | |
--tanrei-comp-navigation-bar-filled-selected-pressed-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-filled-selected-pressed-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-filled-selected-pressed-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-filled-selected-pressed-item-icon-color | — | |
--tanrei-comp-navigation-bar-filled-selected-pressed-item-label-color | — | |
--tanrei-comp-navigation-bar-filled-selected-pressed-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-container-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-icon-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-label-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-focused-item-container-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-focused-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-focused-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-focused-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-focused-item-icon-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-focused-item-label-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-focused-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-container-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-icon-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-label-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-item-container-color | comp.navigation-bar.unselected.item.container.color | |
--tanrei-comp-navigation-bar-filled-unselected-item-covered-icon-color | sys.color.on-primary | |
--tanrei-comp-navigation-bar-filled-unselected-item-covered-label-color | sys.color.on-primary | |
--tanrei-comp-navigation-bar-filled-unselected-item-icon-indicator-color | comp.navigation-bar.unselected.item.icon-indicator.color | |
--tanrei-comp-navigation-bar-filled-unselected-item-icon-color | comp.navigation-bar.unselected.item.icon.color | |
--tanrei-comp-navigation-bar-filled-unselected-item-label-color | comp.navigation-bar.unselected.item.label.color | |
--tanrei-comp-navigation-bar-filled-unselected-item-state-layer-color | comp.navigation-bar.filled-unselected.item.icon.color | |
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-container-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-icon-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-label-color | — | |
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-focused-opacity | — | 1 |
--tanrei-comp-navigation-bar-hovered-opacity | — | 1 |
--tanrei-comp-navigation-bar-item-container-padding-block | sys.space.space50 | 4px |
--tanrei-comp-navigation-bar-item-container-padding-inline | sys.space.space150 | 12px |
--tanrei-comp-navigation-bar-item-container-shape | sys.shape.corner.full | 9999px |
--tanrei-comp-navigation-bar-item-gap | sys.space.space50 | 4px |
--tanrei-comp-navigation-bar-item-icon-indicator-height | comp.navigation-bar.item.icon.size | 24px |
--tanrei-comp-navigation-bar-item-icon-indicator-shape | sys.shape.corner.full | 9999px |
--tanrei-comp-navigation-bar-item-icon-indicator-width | comp.navigation-bar.item.icon.size | 24px |
--tanrei-comp-navigation-bar-item-icon-size | sys.icon.medium | 24px |
--tanrei-comp-navigation-bar-motion-feedback-duration | sys.motion.role.feedback | 450ms |
--tanrei-comp-navigation-bar-motion-selection-duration | sys.motion.role.selection.travel | 250ms |
--tanrei-comp-navigation-bar-motion-selection-easing | sys.motion.easing.spatial | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-navigation-bar-motion-state-duration | sys.motion.role.state.large | 250ms |
--tanrei-comp-navigation-bar-motion-state-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-navigation-bar-outline-color | sys.color.outline-variant | |
--tanrei-comp-navigation-bar-outline-width | sys.border.hairline | 1px |
--tanrei-comp-navigation-bar-pressed-opacity | — | 1 |
--tanrei-comp-navigation-bar-selected-disabled-item-container-color | — | |
--tanrei-comp-navigation-bar-selected-disabled-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-selected-disabled-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-selected-disabled-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-selected-disabled-item-icon-color | — | |
--tanrei-comp-navigation-bar-selected-disabled-item-label-color | — | |
--tanrei-comp-navigation-bar-selected-disabled-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-selected-focused-item-container-color | — | |
--tanrei-comp-navigation-bar-selected-focused-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-selected-focused-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-selected-focused-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-selected-focused-item-icon-color | — | |
--tanrei-comp-navigation-bar-selected-focused-item-label-color | — | |
--tanrei-comp-navigation-bar-selected-focused-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-selected-hovered-item-container-color | — | |
--tanrei-comp-navigation-bar-selected-hovered-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-selected-hovered-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-selected-hovered-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-selected-hovered-item-icon-color | — | |
--tanrei-comp-navigation-bar-selected-hovered-item-label-color | — | |
--tanrei-comp-navigation-bar-selected-hovered-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-selected-item-container-color | sys.color.secondary-container | |
--tanrei-comp-navigation-bar-selected-item-covered-icon-color | sys.color.on-secondary-container | |
--tanrei-comp-navigation-bar-selected-item-covered-label-color | sys.color.on-secondary-container | |
--tanrei-comp-navigation-bar-selected-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-selected-item-icon-color | sys.color.on-secondary-container | |
--tanrei-comp-navigation-bar-selected-item-label-color | sys.color.on-surface | |
--tanrei-comp-navigation-bar-selected-item-state-layer-color | comp.navigation-bar.selected.item.icon.color | |
--tanrei-comp-navigation-bar-selected-pressed-item-container-color | — | |
--tanrei-comp-navigation-bar-selected-pressed-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-selected-pressed-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-selected-pressed-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-selected-pressed-item-icon-color | — | |
--tanrei-comp-navigation-bar-selected-pressed-item-label-color | — | |
--tanrei-comp-navigation-bar-selected-pressed-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-unselected-disabled-item-container-color | — | |
--tanrei-comp-navigation-bar-unselected-disabled-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-unselected-disabled-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-unselected-disabled-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-unselected-disabled-item-icon-color | — | |
--tanrei-comp-navigation-bar-unselected-disabled-item-label-color | — | |
--tanrei-comp-navigation-bar-unselected-disabled-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-unselected-focused-item-container-color | — | |
--tanrei-comp-navigation-bar-unselected-focused-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-unselected-focused-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-unselected-focused-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-unselected-focused-item-icon-color | — | |
--tanrei-comp-navigation-bar-unselected-focused-item-label-color | — | |
--tanrei-comp-navigation-bar-unselected-focused-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-unselected-hovered-item-container-color | — | |
--tanrei-comp-navigation-bar-unselected-hovered-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-unselected-hovered-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-unselected-hovered-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-unselected-hovered-item-icon-color | — | |
--tanrei-comp-navigation-bar-unselected-hovered-item-label-color | — | |
--tanrei-comp-navigation-bar-unselected-hovered-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-unselected-item-container-color | — | |
--tanrei-comp-navigation-bar-unselected-item-covered-icon-color | sys.color.on-secondary-container | |
--tanrei-comp-navigation-bar-unselected-item-covered-label-color | sys.color.on-secondary-container | |
--tanrei-comp-navigation-bar-unselected-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-unselected-item-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-navigation-bar-unselected-item-label-color | sys.color.on-surface-variant | |
--tanrei-comp-navigation-bar-unselected-item-state-layer-color | comp.navigation-bar.unselected.item.icon.color | |
--tanrei-comp-navigation-bar-unselected-pressed-item-container-color | — | |
--tanrei-comp-navigation-bar-unselected-pressed-item-covered-icon-color | — | |
--tanrei-comp-navigation-bar-unselected-pressed-item-covered-label-color | — | |
--tanrei-comp-navigation-bar-unselected-pressed-item-icon-indicator-color | — | |
--tanrei-comp-navigation-bar-unselected-pressed-item-icon-color | — | |
--tanrei-comp-navigation-bar-unselected-pressed-item-label-color | — | |
--tanrei-comp-navigation-bar-unselected-pressed-item-state-layer-color | — | |
--tanrei-comp-navigation-bar-item-label-typescale | sys.typescale.label-small | 500 12px/1.3333 |
フィクスチャ
基準となる描画パターンです。全プラットフォームが同じ ID でスナップショットを撮り、結果を比較できるようにしています。
four-destinations
value: inbox label: Main items: 4 three-destinations
value: search label: Main items: 3 five-destinations
value: library label: Main items: 5 labels-selected
value: sent labelVisibility: selected label: Main items: 4 labels-never
value: inbox labelVisibility: never label: Main items: 4 badges
value: inbox label: Main items: 4 disabled
value: inbox label: Main items: 4 none-selected
label: Main items: 4 selected-disabled
value: starred label: Main items: 4 long-labels
value: subscriptions label: Main items: 5 filled
value: sent variant: filled label: Main items: 4 filled-labels-selected
value: starred variant: filled labelVisibility: selected label: Main items: 4 filled-badges
value: inbox variant: filled label: Main items: 4 filled-selected-disabled
value: starred variant: filled label: Main items: 4 links
value: /search/ label: Main items: 4 links-rtl
value: /search/ label: Main direction: rtl items: 4 links-disabled
value: / label: Main items: 3 生成元 specs/navigation-bar.spec.json, tokens/comp/navigation-bar.tokens.json, design/tokens/
縦に持ったスマートフォンのようなコンパクトな画面で、3〜5 個の最上位の行き先の間を移動するのに使います。「受信」「検索」「プロフィール」のように、一日に何度も戻ってくる場所です。バーはどの最上位の画面にも表示され続け、今どこにいるかを示します。
1 つの画面の中で、同じ対象の複数のビューを切り替えるにはタブを使います。別の画面からたどり着いた画面を離れるには、アプリバーの戻るボタンを使います。詳細の画面では、ナビゲーションバーを表示しないことがよくあります。
| 観点 | ナビゲーションバー | ナビゲーションドロワー(折りたたみ) | ナビゲーションドロワー(展開) |
|---|---|---|---|
| 置き場所 | 下端に沿って | 先頭側の端に沿ったアイコンの列 | 先頭側の端のパネルの中 |
| ウィンドウ | Compact | Medium 以上 | Expanded 以上(スタンダード)、または Compact(モーダル) |
| 行き先の数 | 3〜5 個 | 最上位の行き先とグループ。1 つにつき 1 セル | プロダクトにある分だけ。セクションとグループにまとめます |
| 行き先の形 | アイコンと、その下の 1 行のラベル。アイコンにバッジ | 項目をアイコンの後ろで切った形。アイコンだけで、バッジを付け、名前はツールチップで示します | 1 行の項目。アイコン(省略可)、折り返すラベル、末尾のテキストや内容 |
| ほかに持つもの | なし | ヘッダー:メニューボタンと、アイコンだけに折りたたんだメインアクション | ヘッダー:メニューボタン、タイトルやアカウントの切り替え、メインアクション |
| ネイティブのプラットフォームでの行き先 | タブバーのタブ | 選択状態を持つボタン | 選択状態を持つボタン |
| 位置づけ | レイアウト領域 | レイアウト領域。またはモーダルのドロワーが広がる元になるレイアウト内の列 | スタンダードはレイアウト領域、モーダルはモーダルなオーバーレイ |
- 折りたたんだドロワーがナビゲーションレールです。 折りたためるドロワーは、コンテンツに幅が要るときにアイコンの列まで狭まり、メニューボタンでまた広がります。どのアイコンも位置を変えません。Tanrei の独立したナビゲーションレールは、この形に統合されて廃止されました。
- バーとドロワーは、同じ行き先を置く場所が違うだけです。 同じ行き先と同じ選択を受け取るので、ウィンドウの大きさに合わせて変わるプロダクトは、行き先に手を加えずにブレークポイントでバーとドロワーを切り替えられます。同時に表示するのはどちらか一方だけです。
- ドロワーはもっと多くを持てます。 バーに収まらないほど行き先が多いとき、メールアプリのフォルダーとラベルのように行き先がグループに分かれるときは、展開したドロワーを使います。大きいウィンドウではスタンダードのドロワーをコンテンツの横に置き、コンパクトなウィンドウではモーダルのドロワーをメニューボタンから重ねて開きます。
- モーダルの折りたためるドロワーは、アイコンの列をレイアウトに残したまま、メニューボタンで開くとスクリムの上でコンテンツに重なって広がります。
- どのプラットフォームでも、どちらも行き先の値で選び(Compose は項目ごとのフラグ、Flutter は番号)、
changeとreselectを発火します。
labelVisibility |
どうなるか | 用途 |
|---|---|---|
always(既定) |
すべての行き先がラベルを描きます。 | ほとんどのバー。ラベルがあると、行き先がひと目で分かります。 |
selected |
今の行き先だけがラベルを描きます。ラベルを描かない行き先では、アイコンが行き先の中央に来ます。選択が変わっても、行き先の大きさと位置は変わりません。 | 場所が限られていて、アイコンがよく知られているバー。 |
never |
どの行き先もラベルを描きません。 | 4〜5 個のアイコンが誰にでも分かる、メディアやソーシャルのプロダクト。 |
描かれないラベルも行き先のアクセシブルネームとして使われるので、どの行き先にもラベルが必要です。
- アクティブインジケーター。 どの行き先も 1 つの箱を持ちます。バーの幅を等分した持ち分を、バーの上下と両端から同じ余白(既定のシステムでは 4)だけ内側に収めた箱です。幅 360、高さ 64 のバーに行き先が 4 つあれば、それぞれの箱は 88 × 56 で、既定のシステムではカプセル形です。箱は、中身が何であっても 1 つのバーのどの行き先でも同じ大きさで、アイコンとラベルの大きさに合わせることはありません。今の行き先の箱は塗られてアクティブインジケーターになり、アイコンも塗りで描かれます。ホバー、フォーカス、押下は、どの行き先でもこの同じ箱の中に描きます。
- スライド。 選択が変わると、インジケーターは前の行き先から新しい行き先へ、大きさを保ったままスライドします。アイコンとラベルはインジケーターに覆われたところだけ反転するので、インジケーターが通り過ぎる行き先は一瞬その部分だけ色が変わり、新しい行き先はインジケーターが着いたときにすべて反転します。何も選ばれていない状態からは新しい行き先の上にフェードインし、何も選ばれない状態へはフェードアウトします。伸びたり縮んだりすることはありません。バーが最初に表示されたときや、選択は変わらずに今の行き先の大きさだけが変わったとき(フォントの読み込み、文字の大きさの変更など)は、インジケーターをすぐその位置に置きます。動きを減らす設定では瞬時に切り替わります。
- ドラッグ。 行き先が選ばれている間に、タッチ、ペン、マウスのいずれかでバーを押して横に動かすと、インジケーターがポインターについて動きます。離すまでは何も選ばれません。
value、フォーカス、各アイコンの塗り、読み上げる内容は、今の行き先のままです。離すと、インジケーターが間にある 2 つの行き先のうち近い方、またはフリングした向きの方を選び、changeを 1 回発火します。今の行き先の上に戻して離したときは、reselectを含めて何も発火しません。Escape でドラッグを取り消せます。ドラッグが唯一の方法になることはありません。タップ、クリック、Enter、Space でもこれまでどおり選べます。 - 再選択。 今の行き先をもう一度選ぶと、
changeではなくreselectが発火します。その画面を一番上までスクロールするか、最初のページに戻すのが一般的な対応です。 - バッジ。 件数のあるバッジはその数を(999 を超えると 999+)、件数のないバッジは点を描きます。
badgeLabelは、スクリーンリーダーが行き先の名前の後に読む「未読 3 件」のような言葉です。点には読み上げる数がないので、必ず付けます。バッジは、行き先が無効になっても表示されたままです。
ページが分かれているサイトでは、すべての行き先に href を指定し、バーの value をそのページ自身のアドレスにします。今の行き先と今のページは 1 つのものになり、印も 1 つです。
- 選択するのではなく、たどる。 リンクの通常の操作では、行き先の値と
hrefを持つchange(今の行き先ならreselect)を発火し、そのあとでリンクがたどられます。バーはvalueを書き換えません。アクティブインジケーターはvalueが指す行き先にとどまり、valueが変わったときに動きます。 - ルーター。 自分で移動を扱うには、イベントをキャンセルし、
detail.hrefへ移動してvalueを設定します。インジケーターは新しい行き先へスライドします。 - ほかのタブ。 修飾キーを押しながらのクリックや Enter、中クリック、
targetがnewのリンク、ダウンロードは、ブラウザーのものです。バーは何も発火せず、何も選択しません。 - 新しいコンテキスト。
target: newは新しいタブやウィンドウで開きます。行き先はアイコンとその下のラベルでできていて、印を置く場所がないので、印は描きません。targetLabelが、バッジの説明のあとで、それを支援技術に伝えます。 - ドラッグ。 リンクの上で始めた横方向のドラッグは、アクティブインジケーターを動かします。リンクのアドレスはドラッグしません。リンクの上で離すと、そのリンクを普通に押したことになります。
valueが変わるまで、インジケーターは今の行き先に戻ります。 - 無効。 無効なリンクは、どの方法でもたどれず、フォーカスの順序から外れます。
variant |
今の行き先 | 用途 |
|---|---|---|
tonal(既定) |
セカンダリーコンテナの色の控えめなインジケーターで、アイコンとラベルはそのコンテナの内容の色です。 | ほとんどのバー。 |
filled |
プライマリーの色の強いインジケーターで、その上のアイコンとラベルを反転させます。 | 地図、写真の一覧、プレーヤーなど、にぎやかな画面の上で目立たせる必要のあるバー。 |
インジケーターに覆われていない行き先は、どちらでも同じ見た目です。variant が変えるのは選択の描き方の強さだけで、読み上げる内容は変わりません。Tonal のバーも Filled のバーも、同じ名前、ロール、状態を伝えます。無効になった今の行き先は、Tonal ならインジケーターを保ってその上に無効の内容を描き、Filled なら無効の Filled ボタンと同じくインジケーターを薄くします。プライマリーの色の上の無効の内容は読みにくいからです。
m3-like と cupertino のプリセットには filled がありません。Material のナビゲーションバーにも Apple のタブバーにも、スタイルは 1 つしかないからです。m3-like は Material と同じく、アイコンの後ろだけに箱を置いてラベルをその下に描き、行き先を囲む箱は描きません。cupertino は今のタブを色合いだけで示します。どちらでも、ドラッグしている間に動いて見えるものはありませんが、離したときには行き先が選ばれます。
- ラベルは「受信」「検索」「プロフィール」のような短い 1 語にします。行き先に収まらないラベルは末尾が省略記号で切られ、折り返しはしません。
filledは、Tonal のインジケーターがバーの後ろの画面に埋もれてしまう場所でだけ使います。- 行き先とその順番は、どの最上位の画面でも同じにします。
- すべての行き先で同じスタイルのアイコンを使い、今の行き先の塗りの形はバーに描かせます。
- 「作成」や「共有」のようなアクションをバーに置かない。メインアクションボタンかアプリバーを使います。
- 行き先を 2 個以下や 6 個以上にしない。数はデバッグビルドで報告します。残りはナビゲーションドロワーに移します。
- ときどき使えなくなる行き先を隠さない。無効にして、バーの形を保ちます。
- すべての行き先に一度にバッジを付けない。
- ロール
navigation- ラベル付け
- Each destination is named by its label, drawn or not; its icon is decorative, and so is the covered copy of its icon and label. A badge adds its description (badgeLabel, or the displayed count) after the name, and a link that opens a new context its targetLabel after that. The bar's label names the bar itself.
- 最小ターゲット
- 見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px
キーボード操作
| キー | 動作 |
|---|---|
| Tab | Moves focus to the next destination that is not disabled: each destination is a tab stop, as the links of a navigation region are. |
| Enter / Space | Activates the focused destination (Enter only for a destination that is a link: it follows the link, and Space is left to the platform). |
| Enter with a modifier key, on a link (the web) | What the browser does with a link: another tab or window, a download. The bar emits nothing and selects nothing (NVB-31). |
| Escape | Cancels a pointer drag of the active indicator in progress: the indicator returns and nothing is selected. Otherwise it does nothing. |
備考
- The semantics follow each platform's idiom for a bar of top-level destinations. On the web it is a navigation landmark whose destinations are links (or buttons in an app that does not change the URL) with aria-current="page" on the current one: that is how a web user recognises site navigation, and the tablist pattern would promise tab panels and arrow-key movement that a switch of whole screens does not have. On iOS, Android, Flutter and React Native the same bar is the platform's tab bar (UITabBar, BottomNavigationView and Material's NavigationBar all expose their items as tabs), so each destination is announced as a tab with its selected state and position.
- Selected and the current page are one thing. The bar lists the product's top-level places and the selected one is the place being shown, which the web exposes as aria-current="page" on a button and on a link alike. A site of separate pages changes who says which it is — the page, through value, not a press — and nothing else: a link destination is followed, the bar never selects it by itself, and no destination is ever selected and another current.
- Screen readers announce, for example, "Inbox, tab, 1 of 4, selected, 3 unread": name, role, position, state, then the badge's description.
- Labels do not wrap: a bar's height is part of the screen's layout. A label longer than its item container allows is truncated, and stays whole as the destination's name. At 200% text size the bar and its item containers grow, and iOS shows the large-content viewer on a long press, as its own tab bar does.
- The variant changes how strongly the selection is drawn, not what is announced: a tonal and a filled bar expose the same names, roles and states.
- The drag is never the only way to choose (WCAG 2.5.7): a tap, a click, Enter and Space remain, and the keyboard is unchanged. While a drag lasts nothing a destination exposes changes and nothing is announced; the release announces once, as an activation does. With a screen reader running a one-finger drag is the reader's, and activation works as before.
- How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.
生成元 specs/navigation-bar.spec.json, tokens/sys/measure.tokens.json
- Web では、バーは
labelを名前とするナビゲーションのランドマークです。各行き先はリンク(hrefがあるとき)かボタンで、今の行き先には、リンクでもボタンでも同じくaria-current="page"が付きます。今のページのための 2 つ目の状態はありません。タブリストにはしません。画面全体を切り替えるものにはタブパネルも矢印キーでの移動もないからです。 - iOS、Android、Flutter、React Native では、それぞれのプラットフォームの下端のバーと同じく、タブバーとして伝えます。各行き先はタブのロール(SwiftUI では選択のトレイトを持つボタン)、選択状態、位置を持つ 1 つの要素で、ラベルが描かれていてもいなくても、そのラベルを名前とします。スクリーンリーダーはたとえば「受信、タブ、4 個中 1 個目、選択中、未読 3 件」のように読み上げます。
- アイコンは支援技術から隠します。バッジは、名前と状態の後に自分の説明(
badgeLabel、なければ表示している数)を加え、新しいコンテキストで開くリンクは、その後にtargetLabelを加えます。 - 無効でない行き先は、ナビゲーション領域のリンクと同じく、それぞれが Tab で止まる位置です。Enter と Space でフォーカス中の行き先を実行します(Web のリンクでは Enter だけ)。無効な行き先はフォーカスを受け取らず、無効であることを伝えます。
- フォーカスリングは行き先の箱の周りに、バーの内側に収めて、キーボードフォーカスのときだけ描きます。
- インジケーターをドラッグしても、離すまでは読み上げる内容は何も変わらず、フォーカスも動きません。
- インジケーターに覆われたところに描く、アイコンとラベルの反転したコピーは装飾です。支援技術がたどる行き先はそれぞれ 1 つだけで、
variantによって読み上げは変わりません。 - ヒット領域はセル全体です。バーの高さいっぱいと、幅を等分した 1 つ分で、最小ターゲットを下回りません。行き先に対してバーが狭すぎるときも幅は等分のままで、バーを与えられた幅より広げる代わりに、セルが最小ターゲットより狭くなります。
- 文字を大きくすると、ラベルが 1 行に収まるように、バーと、それに合わせてどの行き先の箱も大きくなります。アイコンは拡大しません。省略されたラベルも、行き先の名前としては全体が使われます。iOS の大きなコンテンツビューアが使える場所では、長押しで行き先のアイコンとラベルを拡大して表示します。
- 行き先が 2 個以下や 6 個以上のとき、ラベルのない行き先、どの行き先も指していない値、重複した値は、デバッグビルドで報告します。新しいコンテキストで開くのに
targetLabelのないリンク、スクリプトを指すhrefや読み取れないhref、バーが定義していないtargetも報告します。
プラットフォームごとの既知の制約:
- SwiftUI、Compose、Flutter、React Native にはリンクの行き先がありません。これらでは、行き先はまだ
hrefを受け取りません。 - Web:すべての寸法が
remに従うので、ブラウザーの文字サイズを大きくすると、項目コンテナとアイコンも大きくなります。Tanrei の文字の大きさだけを変えたときは大きくなりません。 - SwiftUI:セルの最小値はプラットフォーム自身のターゲットで、macOS では 44 ポイントです。仕様が指定しているのは 48 ポイントのタッチターゲットです。
- SwiftUI:iOS 以外では、行き先は位置を値のテキスト(「2 of 4」)として持ち、英語だけです。SF Symbol などの画像のアイコンは選択に従わないので、選ばれた行き先には塗りつぶしのシンボルを利用者側で渡します。大きなコンテンツビューアは組み込んでありますが、まだテストで確かめていません。
- Compose にはバー全体の値がないので、どの行き先も指していない値を報告する手段がありません。何も選ばないときは、どの項目にも
selectedを付けません。 - Flutter のセマンティクスには集合の中の位置がないので、「4 個中 1 個目」は読み上げられません。
- SwiftUI、Flutter、React Native は、アイコンのファミリーと塗りを選択のモーションに合わせず瞬時に切り替えます。
- React Native には大きなコンテンツビューアがありません。行き先は位置を値のテキスト(「1 of 4」)として持ち、
positionTextを渡さなければ英語です。 - React Native:選択のモーション、インセット、TalkBack と VoiceOver の読み上げは、実機ではまだ確かめていません。
- すべてのプラットフォーム(既定のシステム):Tonal のインジケーターとバーのコントラストは約 1.15:1 で、iOS のアクセシビリティ監査で指摘されます。選択は塗りつぶしのアイコンとラベルの色でも示され、それが重要な場所では
filledで 3:1 以上のインジケーターにできます。
- ラベルを描かないバーでも、すべての行き先にラベルを付けます。
labelで「メイン」のようにバーに名前を付けます。画面にほかのナビゲーション領域があるときは特に必要です。- どのバッジにも、何を数えているかが分かる
badgeLabelを付けます。点には必ず付けます。 - 各行き先の値は重複させず、変えないでおきます。
- ページが分かれているサイトでは、どのページでも
valueをそのページの行き先にします。バーは、自分ではリンクを選択しません。 - 新しいタブで開くリンクには、「新しいタブで開きます」のように、プロダクトの言語で
targetLabelを指定します。Tanrei は言葉を同梱しません。 - Compose では、
selectedを付けるのはちょうど 1 つの項目か、どれにも付けないかにします。Flutter では、受け取った番号をselectedIndexと比べて、再選択か変更かを見分けます。 - React Native では、プロダクトの言語で
positionTextを渡し、ウィンドウのインセットをTanreiProviderに渡します。