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.
Edita este ejemplo en vivo en Storybook
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:
- Un chip añadido fuera de pantalla debe anunciarse. La demo se desplaza hasta cada chip nuevo con
apiRef.current.scrollToItem(el, 'smooth', 'end')tras el renderizado; el ejemplo de añadir-elemento-y-desplazarse-hasta-él es exactamente este cableado. - Arrastrar para desplazar, hacer clic para alternar, nunca ambos. Los usuarios de escritorio arrastran la fila como una superficie táctil; soltar sobre un chip no debe activarlo. La receta de arrastre sigue el gesto y suprime justo ese clic.
- Flechas solo cuando son útiles.
useLeftArrowVisible/useRightArrowVisibleestán conectadas al mismo IntersectionObserver que todo lo demás, así que las flechas se desactivan en los bordes reales, incluso después de añadir o quitar chips.
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.
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/:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/chip-bar.json