タブバー
画面下部に固定するモバイル定番のタブバー。アイコン+ラベルの項目をタップすると選択が切り替わり、現在地が藍色で示される。
プレビュー
操作クリック/タップで動きます。
プレビューでは枠内に画面を再現するため .tbar-screen を position:static のカードにしています。実ページで画面下端に貼り付けるときは .tbar を position:fixed; bottom:0; left:0; right:0 にし、本文側へタブバー高さぶんの padding-bottom を確保してください。safe-area-inset-bottom で iPhone のホームバーぶんを避けています。
使いどころ
モバイルアプリのように、ホーム・検索・お気に入りなど独立した数個(4〜5個程度)の主要画面を、常に手の届く位置から一発で切り替えたい場面に向く。画面下部に固定してどのページからも同じ位置で操作できるのが利点で、階層の浅いアプリのグローバルナビゲーションとして定番の形。逆に、切替先が7個を超える、または利用頻度に大きな差がある(滅多に使わない機能を同列に並べる)構成には向かない。tbar-tabはflex:1で均等割りされるため、項目が増えるほど1個あたりの幅とラベルの可読性が犠牲になる。そうした場合はハンバーガーメニューやサイドバーナビと組み合わせ、タブバーには本当によく使う項目だけを絞り込むべき。
仕組み
5個のtbar-tabボタンをrole="tablist"を持つnav要素でまとめ、各ボタンにrole="tab"とaria-selectedを付けている。クリック時はtabs.forEachで全ボタンを走査し、押されたボタンだけis-activeとaria-selected="true"にし、他は両方falseへ戻すという単純な排他制御。アイコンはSVGをインラインで直接埋め込んでおり、外部アイコンフォントやスプライトシートに依存しない。このデモではページ本体(tbar-body)の中身をpageLabel.textContent書き換えだけで差し替える見本になっているが、実アプリでは各タブごとに別のビュー(もしくは別ルート)を表示する処理に置き換えることになる。noteにある通り、プレビューでは.tbar-screenをposition:staticの枠内カードとして再現しているだけで、実ページに組み込む際は.tbarをposition:fixed; bottom:0; left:0; right:0にし、本文側にタブバー高さぶんのpadding-bottomを確保する必要がある。
実装の注意
最も見落としやすいのは、role="tab"という選択が用途次第で誤りになる点。ARIAのTabsパターンは『同一画面内のtabpanelを切り替える複合ウィジェット』のためのもので、ボトムバーの典型用途である『別ルートへの遷移』に使うと、スクリーンリーダーにはページ内タブのように案内されるのに実際はページごと替わる、というちぐはぐな体験になる。ルート遷移ならrole=tabをやめてnav内のリンク+aria-current="page"にするのが正しく、逆に同一画面内のビュー切り替えとして使うなら、tabpanelとの対応付け(aria-controls/aria-labelledby)と左右矢印・Home/Endのキーボード操作まで揃えて初めてTabsパターンとして成立する。この見本はどちらにも振り切っていない中間形である。もう一つ、position:fixedで画面下部に固定する実装に変えたとき、本文側にpadding-bottomを確保し忘れると、タブバーの下にコンテンツが隠れてスクロールしないと読めなくなる典型的な事故が起きやすい。safe-area-inset-bottomはCSSで確保済みだが、これはあくまでiPhoneのホームバー避けであり、本文側の被り防止とは別問題である点も混同しないこと。
アクセシビリティ
実装へ落とすときは、まず用途でセマンティクスを選び分けること。画面ごと切り替わるグローバルナビ(別ルート)として使うならrole=tablist/tab/aria-selectedは使わず、nav要素の中のa要素+現在地にaria-current="page"が正しい型で、ブラウザとスクリーンリーダーの既定のリンク挙動がそのまま活きる。同一画面内のパネル切り替えとして使う場合に限りTabsパターンを採り、その場合はtabpanelへのaria-controls(tabpanel側にaria-labelledby)と、ローミングtabindexによる左右矢印・Home/Endのフォーカス移動まで実装して完成になる。もう一つの弱点は、タブ切り替え後にpageLabel.textContentが変わっても、それを読み上げるaria-live領域が無いこと。画面が切り替わったこと自体はaria-selectedの変化で伝わるが、切り替わった先の内容を能動的に読み上げさせたいなら、切り替え先のコンテナへfocus()を当てるか、role="status"の領域で明示的に案内するとよい。
調整して使う
アイコン+ラベルの2段組みが基本形だが、ラベルを省いてアイコンだけにする、逆にラベルだけのテキストタブに寄せるといった調整ができる。バッジ(未読件数の丸数字)を特定のタブに重ねて表示する拡張もよく使われ、その場合はaria-labelに『通知、未読3件』のように件数を含めて音声でも伝わるようにする配慮が要る。中央のタブだけ円形に浮き上がらせてFABのように強調するデザイン(SNSアプリの投稿ボタンなど)も定番のバリエーションで、その場合はis-active切替のロジックとは別に、中央ボタンだけ独立したクリックハンドラ(投稿画面を開く等)を持たせる設計になることが多い。
コード(コピーして使えます)
<div class="tbar-screen">
<div class="tbar-body" id="tbar-body">
<p class="tbar-page">ホーム画面</p>
</div>
<nav class="tbar" role="tablist" aria-label="メインナビゲーション">
<button class="tbar-tab is-active" role="tab" aria-selected="true" data-page="ホーム">
<svg class="tbar-ic" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V20a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9.5"/></svg>
<span class="tbar-label">ホーム</span>
</button>
<button class="tbar-tab" role="tab" aria-selected="false" data-page="さがす">
<svg class="tbar-ic" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg>
<span class="tbar-label">さがす</span>
</button>
<button class="tbar-tab" role="tab" aria-selected="false" data-page="お気に入り">
<svg class="tbar-ic" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20.5 4.5 13a4.5 4.5 0 0 1 7.5-4.9A4.5 4.5 0 0 1 19.5 13z"/></svg>
<span class="tbar-label">お気に入り</span>
</button>
<button class="tbar-tab" role="tab" aria-selected="false" data-page="通知">
<svg class="tbar-ic" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 6-3 7-3 7h18s-3-1-3-7"/><path d="M13.5 21a2 2 0 0 1-3 0"/></svg>
<span class="tbar-label">通知</span>
</button>
<button class="tbar-tab" role="tab" aria-selected="false" data-page="マイページ">
<svg class="tbar-ic" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 3.5-6 8-6s8 2 8 6"/></svg>
<span class="tbar-label">マイページ</span>
</button>
</nav>
</div>