Một thanh chip lọc có thể cuộn, trong React

Dải chip bên dưới mọi thanh tìm kiếm — chủ đề YouTube, bộ lọc cửa hàng, bộ chọn tag — là một vùng chứa cuộn một dòng đầy các nút bật/tắt. 10% khó là những gì xảy ra ở các cạnh: chip mới xuất hiện ngoài màn hình, thao tác kéo không được bật/tắt bất cứ thứ gì, và mũi tên biết khi nào chúng vô nghĩa.

Thêm một bộ lọc — dải sẽ tự cuộn chip mới vào tầm nhìn.

Trường hợp biên chính là tính năng

Bất kỳ dải flex nào với overflow-x: auto đều cuộn được. Một thanh chip chứng minh giá trị của nó ở các chi tiết:

Trạng thái vẫn nằm trong tay bạn

Thư viện chỉ cuộn; nó không sở hữu lựa chọn. Chip là các nút của bạn — aria-pressed cho các nút bật/tắt đa lựa chọn, state thuần cho đơn lựa chọn — và menu chỉ cần mỗi chip mang một itemId. Điều đó nghĩa là trạng thái chip kết hợp được với bất cứ thứ gì bạn đã có: URL search params, một thư viện form, một mô hình lọc do server điều khiển. Xóa một chip là xóa một item; tạo hiệu ứng biến mất là ví dụ hoạt ảnh item.

Di động: một cảnh báo về cuộn body

Trên màn hình cảm ứng, một thao tác vuốt ngang bên trong thanh có thể kéo cả trang theo hướng ngang trên một số trình duyệt. Nếu bạn gặp điều đó, ví dụ ngăn cuộn body trình bày touch-action và overscroll containment để khóa nó lại — chỉ dùng CSS, không cần thư viện cử chỉ.

Mẫu hình, tối giản

Chip là các nút bật/tắt với itemId; một ref đến menu API sẽ cuộn chip vừa thêm vào tầm nhìn.

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

Hoặc cài đặt nó như một component shadcn

Registry item chip-bar cung cấp cái này như một controlled component — options, selected, onSelectedChange — được style bằng Tailwind trong components/ui/ của bạn:

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

Các ví dụ liên quan

Tất cả 21 ví dụ