---
title: "React filter chips in a scrollable bar"
description: "A horizontal filter-chip bar in React: chips scroll natively, adding a chip scrolls it into view, drag to scroll without breaking clicks. Live demo and source."
canonical: "https://react-horizontal-scrolling-menu.dev/filter-chips"
image: "https://react-horizontal-scrolling-menu.dev/og.png"
---

# A filter-chip bar that scrolls, in React

The chip row under every search bar — YouTube topics, store filters, tag pickers — is a single-line scroll container full of toggle buttons. The hard 10% is what happens at the edges: new chips appearing off-screen, drags that must not toggle anything, and arrows that know when they are pointless.

react

typescript

scrolling

menu

gallery

tabs

carousel

slider

accordion

lightbox

Add filter

Add a filter — the row scrolls the new chip into view by itself.

[Edit this example live in Storybook](https://asmyshlyaev177.github.io/react-horizontal-scrolling-menu/?path=/story/examples-additemandscrolltoit--add-item-and-scroll-to-it)

## The edge cases are the feature

Any flex row with `overflow-x: auto` scrolls. A chip bar earns its keep on the details:

-   **A chip added off-screen must announce itself.** The demo scrolls to every new chip with `apiRef.current.scrollToItem(el, 'smooth', 'end')` after render — the [add-item-and-scroll-to-it example](https://react-horizontal-scrolling-menu.dev/examples/add-item-and-scroll-to-it.md) is exactly this wiring.
-   **Drag to scroll, click to toggle — never both.** Desktop users drag the row like a touch surface; releasing over a chip must not flip it. The [drag recipe](https://react-horizontal-scrolling-menu.dev/examples/mouse-drag.md) tracks the gesture and suppresses that one click.
-   **Arrows only when useful.** `useLeftArrowVisible` / `useRightArrowVisible` are wired to the same IntersectionObserver as everything else, so arrows disable at the real edges — including after chips are added or removed.

## State stays in your hands

The library scrolls; it does not own selection. Chips are your buttons — `aria-pressed` for multi-select toggles, plain state for single-select — and the menu only needs each one to carry an `itemId`. That means chip state composes with whatever you already have: URL search params, a form library, a server-driven filter model. Deleting a chip is [removing an item](https://react-horizontal-scrolling-menu.dev/examples/add-items.md); animating it out is the [items-animation example](https://react-horizontal-scrolling-menu.dev/examples/items-animation.md).

## Mobile: one warning about body scroll

On touch screens a horizontal swipe inside the bar can drag the page sideways with it on some browsers. If you see that, the [prevent-body-scroll example](https://react-horizontal-scrolling-menu.dev/examples/prevent-body-scroll.md) shows the `touch-action` and overscroll containment to lock it down — CSS only, no gesture library.

## The pattern, minimal

Chips are toggle buttons with an `itemId`; a ref to the menu API scrolls a newly added chip into view.

ChipBar.tsx

```
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>
  );
}
```

## Or install it as a shadcn component

The [chip-bar](https://react-horizontal-scrolling-menu.dev/r/chip-bar.json) registry item ships this as a controlled component — `options`, `selected`, `onSelectedChange` — Tailwind-styled in your `components/ui/`:

shadcn

```
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/chip-bar.json
```

## Related examples

-   [Add an item and scroll to itThe filter-chips pattern: append, then bring into view.](https://react-horizontal-scrolling-menu.dev/examples/add-item-and-scroll-to-it.md)
-   [Load more when the end shows upInfinite append driven by last-item visibility.](https://react-horizontal-scrolling-menu.dev/examples/add-items.md)
-   [Prevent body scrollWheel over the menu scrolls the menu, not the page.](https://react-horizontal-scrolling-menu.dev/examples/prevent-body-scroll.md)

[All 21 examples](https://react-horizontal-scrolling-menu.dev/examples.md)

---

More examples: <https://react-horizontal-scrolling-menu.dev/examples.md>
Library summary for LLMs: <https://react-horizontal-scrolling-menu.dev/llms.txt>
