ブラウザのようにスクロールする 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 を持つ普通のボタンで、選択すると中央に来ます。これがアイデアの全体です — 上のデモはスタイリングとドラッグを追加しています。

ScrollTabs.tsx
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 レジストリアイテムはこのパターンをデータ駆動で提供します — tabsvalueonValueChange を渡す — あなたの components/ui/ に置く編集可能なコンポーネントとして:

shadcn
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/scroll-tabs.json

関連する例

すべての例(21 件)