Una barra de chips de filtro que se desplaza, en React

La fila de chips bajo cualquier barra de búsqueda —temas de YouTube, filtros de tienda, selectores de etiquetas— es un contenedor de scroll de una sola línea lleno de botones de alternar. El 10% difícil es lo que pasa en los bordes: chips nuevos que aparecen fuera de pantalla, arrastres que no deben activar nada y flechas que saben cuándo son inútiles.

Añade un filtro: la fila desplaza sola el nuevo chip hasta que sea visible.

Los casos límite son la característica

Cualquier fila flex con overflow-x: auto se desplaza. Una barra de chips se gana el puesto en los detalles:

El estado queda en tus manos

La librería desplaza; no controla la selección. Los chips son tus botones —aria-pressed para alternar en selección múltiple, estado normal para selección única— y el menú solo necesita que cada uno lleve un itemId. Eso significa que el estado de los chips encaja con lo que ya tengas: parámetros de búsqueda en la URL, una librería de formularios, un modelo de filtro gestionado por el servidor. Eliminar un chip es quitar un elemento; animarlo al salir es el ejemplo de animación de elementos.

Móvil: un aviso sobre el scroll del body

En pantallas táctiles, un deslizamiento horizontal dentro de la barra puede arrastrar la página entera con él en algunos navegadores. Si ves eso, el ejemplo de prevenir el scroll del body muestra el touch-action y la contención de overscroll para bloquearlo: solo CSS, sin librería de gestos.

El patrón, en su forma mínima

Los chips son botones de alternar con un itemId; una ref a la API del menú desplaza un chip recién añadido hasta que sea visible.

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

O instálalo como componente shadcn

El elemento del registro chip-bar lo distribuye como componente controlado —options, selected, onSelectedChange— con estilos Tailwind en tu components/ui/:

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

Ejemplos relacionados

Todos los ejemplos (21)