React で作る、スクロールするフィルターチップバー
検索バーの下にあるチップ行 — YouTube のトピック、ストアのフィルター、タグピッカー — は、トグルボタンで埋め尽くされた 1 行のスクロールコンテナです。難しいのは残り 10% にあたる端の挙動です:画面外に現れる新しいチップ、何もトグルしてはいけないドラッグ、そして無意味なときを知っている矢印。
フィルターを追加してみてください — 行が新しいチップを自動で見える位置までスクロールします。
エッジケースこそが機能
overflow-x: auto を持つフレックス行なら何でもスクロールします。チップバーが真価を発揮するのは細部です:
- 画面外に追加されたチップは、自分の存在を知らせなければなりません。 デモではレンダリング後に
apiRef.current.scrollToItem(el, 'smooth', 'end')で新しいチップごとにスクロールしています — add-item-and-scroll-to-it の例 はまさにこの配線です。 - スクロールはドラッグで、トグルはクリックで — 両方同時には起きません。 デスクトップのユーザーはタッチ面のようにこの行をドラッグします。チップの上で指を離しても、それを切り替えてはいけません。ドラッグのレシピ がジェスチャーを追跡し、そのクリックだけを抑制します。
- 矢印は役立つときだけ。
useLeftArrowVisible/useRightArrowVisibleは他のすべてと同じ IntersectionObserver に配線されているので、矢印は実際の端で無効になります — チップの追加・削除後も含めて。
状態はあなたの手の中に
ライブラリはスクロールを担当し、選択状態は所有しません。チップはあなたのボタンです — 複数選択トグルには aria-pressed、単一選択には普通の state — メニュー側はそれぞれが itemId を持つことしか必要としません。つまりチップの状態は、URL の検索パラメータ、フォームライブラリ、サーバー駆動のフィルターモデルなど、すでにあるものと自然に組み合わさります。チップの削除は アイテムの削除、アニメーションで消すのは items-animation の例 です。
モバイル:ボディスクロールに関する 1 つの注意
タッチスクリーンでは、バー内での横方向のスワイプが、一部のブラウザではページごと横に引きずってしまうことがあります。それを見かけたら、prevent-body-scroll の例 が touch-action とオーバースクロールの封じ込めでそれを止める方法を示しています — CSS のみで、ジェスチャーライブラリ不要です。
パターン、最小構成
チップは itemId を持つトグルボタンで、メニュー API への ref が新しく追加されたチップを見える位置までスクロールします。
function ChipBar({ options }: { options: string[] }) {
const apiRef = React.useRef<publicApiType>(null);
const [selected, setSelected] = React.useState<string[]>([]);
const toggle = (id: string) =>
setSelected((cur) =>
cur.includes(id) ? cur.filter((c) => c !== id) : [...cur, id],
);
// A chip appended off-screen scrolls itself into view.
const addChip = (id: string) => {
toggle(id);
requestAnimationFrame(() => {
const el = apiRef.current?.getItemElementById(id);
if (el) apiRef.current?.scrollToItem(el, 'smooth', 'end');
});
};
return (
<ScrollMenu apiRef={apiRef}>
{options.map((id) => (
<Chip itemId={id} key={id} pressed={selected.includes(id)}
onToggle={() => toggle(id)} />
))}
</ScrollMenu>
);
}または shadcn コンポーネントとしてインストール
chip-bar レジストリアイテムは、これを制御コンポーネントとして提供します — options、selected、onSelectedChange — あなたの components/ui/ に置く Tailwind スタイルとして:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/chip-bar.json