「今いる場所を変えずに、見る側面だけを変える」というメンタルモデルをユーザーに提供するコンテンツ切り替えUI。
この記事を読むと、NavigationとTabsの使い分け・ARIAロール設計・キーボード操作の実装判断が自分でできるようになります。
1. UI例(Preview / Live)
実装で見る(GunjoUI)
この部品を、デザインシステム GUNJO の実装で確かめられます。
2. 定義(Definition)
Tabs(タブ)とは、単一のビュー内で関連する複数のコンテンツパネルを切り替えて表示するナビゲーション部品。
近いUIとの違い:Global Navigationは「ページ間の移動」、Accordionは「縦方向の展開・折りたたみ」、Tabsは「同一画面内での横並び切り替え」。
3. 使い分け(When to use / When NOT to use)
3.1 When to use
- 同じエンティティ(例:設定画面・ユーザープロフィール)に対して、異なる切り口の情報を整理したい場合
- ユーザーがページ遷移せずに情報の側面を素早く比較・確認したい場合
- タブ間のコンテンツが相互に排他的(被らない内容)かつ論理的に対等な場合
- コンテンツの全体量が多く、一度に表示するとスキャンしにくい場合
3.2 When NOT to use
- サイト全体の主要ページ移動(Home / About / Contact など)→ Global Navigationを使う
- タブ切り替えで全く関係のない文脈・機能へ飛ぶ場合 → リンクを使う
- タブ間で完了・保存が必要なデータの流れがある場合 → Wizard / Stepperを使う
- タブが5〜6個を超える場合(特にモバイル)
3.3 代替UI(Alternatives)
- 縦方向の折りたたみが必要 → Accordion
- モバイルでタブが入り切らない → Select(セレクト / プルダウンメニュー)に降格
4. 設計判断の核(Decision Principles)
「ファイルフォルダーの見出し」と同じように、平行関係にある情報を整理する。
- 遷移か切り替えかを最初に決める: URLが変わるならNavigation、変わらないならTabs
- タブは対等な関係で: 重要度の異なる内容をタブに並べない。階層関係がある場合はツリーやネストメニューが適切
- ラベルは1〜2語で完結: タブのラベルが長いと、全体像の把握が困難になる
- Active状態を常に1つ: 未選択タブが存在する状態を作らない
- ネストは原則禁止: タブの中にタブを作るとユーザーが現在地を失う
5. 状態設計(States)
5.1 必須状態
- Default(通常): 未選択のタブ。クリッカブルであることを示すhover効果が必要
- Selected / Active(選択済): 現在開いているタブ。下線インジケーターや背景色で最も目立たせる(必須)
- Focus(フォーカス): キーボード操作中。明確なアウトラインを表示する(必須)
5.2 条件付き状態
- Hover(ホバー): マウスオーバー時に色・下線を変えてクリッカブルを示す
- Disabled(無効): そのコンテンツが現在利用できない場合にグレーアウト
5.3 State Gallery
| 状態 | 見た目 | 意図 |
|---|---|---|
| Default | テキスト + 透明下線 | 選択可能 |
| Active | テキスト強調 + 有色下線 | 現在表示中 |
| Hover | テキスト色変化 | クリッカブルの予告 |
| Focus | アウトライン | キーボード操作中 |
| Disabled | グレーアウト | 現在利用不可 |
6. バリエーション設計(Variants)
- 下線型(Line): タブ下部にインジケーターバー。最も汎用的。コンテンツページ・設定画面に適する
- Pill型(Segment): 選択タブに背景色。切り替えの視認性が高い。ダッシュボードの集計期間切り替えなどに有効
- カード型: タブをカード形状にし、選択状態で浮き上がるスタイル。メールクライアントやIDE的なUIに適する
禁止パターン:多段タブ(2行以上)・タブ内にタブ(ネスト)・タブ数が多すぎて横スクロール必須
7. パターン集(Good / Bad / How to fix)
7.0 よく崩れる設計パターン
- ページ遷移との混同: タブクリックでURL遷移し、タブバーが消える → Navigationとして設計し直すべき
- ネスト地獄: タブの中にタブ → 現在地が不明になりユーザーが迷う
- 多段タブ: タブが2行以上に折り返す → タブ数を減らすか、Select降格を検討する
7.1 Bad
- タブをクリックするとページ全体がリロードされ、レイアウトが変わる
- タブが2段になっており、下段タブを押すと上段の並びも変わってしまう
7.2 Good
- タブクリックは即座にパネルのみを切り替え、タブバーは常に固定位置
- URLパラメータ(
?tab=specs)と連動し、リロード・共有後も同じタブが開く
7.3 How to fix
- ページ遷移が発生しているならNavigationとしてデザインを改める
- タブバーを固定し、変化するのは下部のTabPanelだけになるようDOM構造を整理する
role="tablist"/role="tab"/role="tabpanel"のARIA定義と矢印キー移動を実装する
8. ルール(Must / Better)
Must(守らないと壊れる)
Better(品質が跳ねる)
9. アクセシビリティ要件(必須)
Keyboard
TabでTabListにフォーカス。TabList内では←→矢印キーでタブ間を移動するEnter/Spaceでパネルを切り替える(またはフォーカス移動で即時切り替え)TabでTabPanelの内容へ移動する
Focus
- アクティブなタブに明確なフォーカスリング(アウトライン)を表示する
Screen Reader
- TabListコンテナに
role="tablist"、各タブにrole="tab"、各パネルにrole="tabpanel"を付与する - 現在開いているタブに
aria-selected="true"、残りにaria-selected="false"を設定する - タブとパネルを
aria-controls(tab → panel)とaria-labelledby(panel → tab)で紐付ける
Touch / Pointer
- タブアイテムの高さは最低 44px を確保する
- タブ間に十分な余白を取り誤タップを防ぐ
Contrast / Readability
- Active状態のインジケーター色は背景に対して 3:1以上 のコントラスト比を確保する
10. 実装メモ(Implementation Notes)
- Reactでの状態管理は
useStateで activeタブIDを管理。パネルはhidden属性または条件レンダリングで切り替える - URLパラメータ連動は
useSearchParams(Next.js)またはURLSearchParamsで実装。router.replaceを使いブラウザ履歴を汚さない - shadcn/ui の
<Tabs>コンポーネントはARIA実装済みのため、カスタム実装より優先して使う - Disabled タブは
aria-disabled="true"を使い、disabled属性はネイティブボタン以外では効かないことに注意
11. 関連リンク
- 関連するUIデザイン原則: 情報アーキテクチャ (Information Architecture), チャンク化 (Chunking), 明確性と確実性 (Clarity & Certainty)
- 用語集(定義): アクセシビリティ (Accessibility)
- 関連するUIコンポーネント(横): Select(セレクト / プルダウンメニュー), Pagination(ページネーション)
まとめ
Tabsの設計は「遷移か切り替えか」を最初に決めることで9割決まります。迷ったら 4. 設計判断の核 に戻り、URLが変わるかどうかを確認してください。ARIAロール(tablist / tab / tabpanel)とキーボード矢印移動を実装することが、アクセシブルなTabsの最低ラインです。