ブラウザのようにスクロールする React のスクロールタブ
タブの並びは、プロダクトが 6 個を超えたとたんに収まらなくなります。解決策はフォントを小さくすることではなく、スクロールする帯にすることです:はみ出し分はブラウザに任せ、タブをクリックすると中央に寄り、矢印は行き先がある時だけ表示されます。
端に近いタブをクリックしてみてください — 自動的に中央までスクロールします。
重要な挙動はただ 1 つ:選択時に中央へ
スクロール可能なタブの帯は、端のタブをクリックしたときに何が起きるかで成否が決まります:両側の隣接タブが見えるように、中央へ滑らかに移動するべきです。ここでは呼び出しはたった 1 つ — scrollToItem(el, 'smooth', 'center') — center-on-click の例 に組み込まれています。マウント時にアクティブなタブを復元するのも同じ呼び出しで、'auto' を使ったものが 位置の保存と復元 にあります。
矢印は同じ可視性データから来ています:useLeftArrowVisible は最初のタブが画面外にある間だけ false になるので、左矢印はまさに役立つときにレンダリングされます。計測コードも、自前の resize observer も不要です。
MUI のスクロールタブでは物足りなくなってきたら
Material UI の variant="scrollable" タブは、Material のデザインシステムの中では正しい答えです — あなたの「タブ」がタブでなくなるまでは。MUI は帯を Tabs のセマンティクスに溶接しています:value/onChange のペア、タブパネル、そしてモバイルではデフォルトで非表示になるスクロールボタン。行にチップやカード、アバター、混在コンテンツを入れたい瞬間、ドラッグスクロールが必要な瞬間、どのアイテムが見えているか知りたい瞬間から、あなたはコンポーネントを使うのではなく、それと戦うことになります。
このライブラリはその下の層です:可視性トラッキング付きのスクロールする行で、「タブ」が何であるべきかについての意見は持ちません。あなたのタブは itemId を持つ任意のコンポーネントです — Tailwind でも、MUI 自身の styled でも、素の CSS でもスタイリングできます。選択状態はあなたの手元に残ります。上のデモが 1 つの useState に保持しているのとまったく同じです。
アクセシビリティはほぼ無料 — 2 つの隙間に注意
帯はネイティブのスクロールコンテナなので、キーボードフォーカス、スクリーンリーダーの読み上げ順序、RTL はすべてプラットフォームから得られます — タブ間をフォーカス移動するとコード不要で自動的にスクロールして見えるようになり、RTL も設定不要です。他のタブ UI と同様、2 つだけはあなたの責任です:ARIA パターンを選ぶこと(実際のパネルが切り替わるなら role="tablist"、「タブ」がナビゲーションならば aria-current)、そして ドラッグスクロール レシピのクリック抑制を保ち、ドラッグの終了がタブを誤って有効化しないようにすることです。
パターン、最小構成
タブは itemId を持つ普通のボタンで、選択すると中央に来ます。これがアイデアの全体です — 上のデモはスタイリングとドラッグを追加しています。
function ScrollTabs({ tabs }: { tabs: string[] }) {
const [active, setActive] = React.useState(tabs[0]);
return (
<ScrollMenu LeftArrow={LeftArrow} RightArrow={RightArrow}>
{tabs.map((tab) => (
<Tab itemId={tab} key={tab} label={tab}
active={tab === active} onSelect={() => setActive(tab)} />
))}
</ScrollMenu>
);
}
function Tab({ itemId, label, active, onSelect }: TabProps) {
const api = React.useContext<publicApiType>(VisibilityContext);
return (
<button
aria-current={active}
onClick={(ev) => {
onSelect();
// The behavior that makes tabs feel native: selecting an
// edge tab glides it to the center of the strip.
api.scrollToItem(ev.currentTarget, 'smooth', 'center');
}}
>
{label}
</button>
);
}または shadcn コンポーネントとしてインストール
scroll-tabs レジストリアイテムはこのパターンをデータ駆動で提供します — tabs、value、onValueChange を渡す — あなたの components/ui/ に置く編集可能なコンポーネントとして:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/scroll-tabs.json