El menú horizontal que sabe qué es visible

Un menú de desplazamiento de React construido sobre el propio desplazamiento del navegador: seguimiento de visibilidad por elemento, flechas, arrastre y una API imperativa completa. 5,7 kB en gzip.

Sci-Fi
Comedy
Drama
Horror
Docs
Kids
Action
Indie
Anime
Classics
Thriller
Reality

getVisible() → ['scifi', 'comedy', 'drama', 'horror', 'docs', 'kids']

This is the library, live — drag it. Dimmed tiles are the ones useIsVisible reports as off-screen.

npm install react-horizontal-scrolling-menu347k descargas/mes5,7 kB min+gzipReact 16.8 – 19MIT

Autoplay, sin motor de carrusel

No hay una prop autoplay — este carril es una receta sobre la API pública: la fila clonada en ambos extremos, un salto de scrollLeft en la costura y un temporizador que llama a scrollNext(). Se pausa al pasar el ratón, con el foco y en pestañas ocultas, se queda quieto bajo movimiento reducido — y puedes arrastrarlo, incluso hacia atrás, a través de la costura.

Tokyo
Oslo
Lima
Cairo
Sydney
Quito
Seoul
Porto
Denver
Hanoi

Un menú, no un carrusel

Embla, Swiper y keen-slider reimplementan el desplazamiento en JavaScript para construir sliders de imágenes: puntos de ajuste, física de muelle, un bucle de render. Esta biblioteca no trae nada de eso. Usa el desplazamiento nativo del navegador y añade lo único que el navegador no da: saber exactamente qué elementos hay en pantalla.

La herramienta equivocada para un slider de imágenes a pantalla completa: usa Embla o Swiper allí. La herramienta correcta para filas de categorías, pestañas, filtros de chips y cualquier fila de cosas sobre la que tu aplicación necesite razonar.

Desplazamiento nativo

La inercia, la barra de desplazamiento, el tacto, la rueda y la accesibilidad vienen del navegador, no de un motor de física. La fila se desplaza antes de que tu JavaScript hidrate: cada demo de esta página está renderizada en el servidor.

Seguimiento de visibilidad

IntersectionObserver informa de qué elementos hay en pantalla. useIsVisible(itemId) suscribe un componente a un elemento: sin matemática de posición de desplazamiento, y solo se vuelven a renderizar los elementos afectados.

Imperativo cuando lo necesitas

scrollToItem, scrollNext, scrollPrev, búsqueda por id o índice — a través del contexto dentro del menú, o de apiRef desde fuera.

Tus componentes, tu CSS

Las flechas, la cabecera, el pie y cada elemento son componentes que escribes tú. El ancho del elemento es tu CSS. La biblioteca trae 210 bytes de estilos de diseño y no se interpone.

Inicio rápido

Un archivo, sin configuración: elementos con itemId, dos flechas leyendo VisibilityContext y la importación de la hoja de estilos.

App.tsx
import React from 'react';
import {
  ScrollMenu,
  VisibilityContext,
  type publicApiType,
} from 'react-horizontal-scrolling-menu';
import 'react-horizontal-scrolling-menu/dist/styles.css';

const items = Array.from({ length: 10 }, (_, i) => `item-${i + 1}`);

export function App() {
  return (
    <ScrollMenu LeftArrow={LeftArrow} RightArrow={RightArrow}>
      {items.map((id) => (
        <Card itemId={id} key={id} title={id} />
      ))}
    </ScrollMenu>
  );
}

function LeftArrow() {
  const visibility = React.useContext<publicApiType>(VisibilityContext);
  const isFirstVisible = visibility.useIsVisible('first', true);
  return (
    <button
      disabled={isFirstVisible}
      onClick={() => visibility.scrollPrev()}
    >

    </button>
  );
}

function RightArrow() {
  const visibility = React.useContext<publicApiType>(VisibilityContext);
  const isLastVisible = visibility.useIsVisible('last', false);
  return (
    <button
      disabled={isLastVisible}
      onClick={() => visibility.scrollNext()}
    >

    </button>
  );
}

function Card({ itemId, title }: { itemId: string; title: string }) {
  const visibility = React.useContext<publicApiType>(VisibilityContext);
  const isVisible = visibility.useIsVisible(itemId);
  return (
    <div className="card" data-visible={isVisible}>
      <div>{title}</div>
      <div>visible: {String(isVisible)}</div>
    </div>
  );
}

The code on the left, running:

item-1
visible: true
item-2
visible: true
item-3
visible: true
item-4
visible: false
item-5
visible: false
item-6
visible: false
item-7
visible: false
item-8
visible: false
item-9
visible: false
item-10
visible: false

itemId es obligatorio en cada elemento: así funciona el seguimiento. La key de React funciona como respaldo.

styles.css es una importación aparte; el bundle JS nunca inyecta CSS.

El ancho del elemento viene de tu propio CSS: el menú no mide nada.

Leer el ejemplo completo de primeros pasos

O pásaselo a tu agente de código

Los modelos entrenados con versiones antiguas siguen buscando visibleElements, elementos Separator y una prop Arrows — todo eliminado hace años — e inventan una prop autoplay que nunca existió. Para evitarlo, el paquete incluye ocho archivos SKILL.md: guías por tareas que tu agente carga bajo demanda a través de TanStack Intent, versionadas con la biblioteca en lugar de con esta página.

npx @tanstack/intent@latest install

Ejecútalo una vez en un proyecto que ya tenga el paquete instalado. Tu agente descubre entonces las habilidades desde node_modules/react-horizontal-scrolling-menu/skills/.

  • menu-setupUn primer menú funcional, flechas, la importación CSS requerida
  • menu-visibilityQué hay en pantalla y el estado de las flechas en los extremos
  • menu-scrollingscrollToItem, apiRef, paginación página a página
  • menu-interactionsArrastre, rueda y tacto — y sus fábricas de manejadores
  • menu-recipesAutoplay, bucle infinito, cargar más: recetas, no props
  • menu-transitions-rtlTiempo de animación, easing personalizado, derecha a izquierda
  • menu-testing-ssrNext.js y RSC, mocks de Jest, Playwright
  • menu-migrationActualizar código anterior a v8 y las API que los modelos aún inventan

Qué hay en la caja

  • Hooks de visibilidad por elemento — useIsVisible(itemId)
  • Helpers first / last para el estado de las flechas
  • scrollToItem · scrollNext · scrollPrev
  • apiRef para el control desde fuera del menú
  • Entrada por arrastre, rueda, tacto y barra de desplazamiento
  • Detección dinámica de añadir/eliminar
  • Ranuras Header y Footer
  • Helpers de paginación slidingWindow + getItemsPos
  • Soporte de derecha a izquierda
  • Funciones de transición personalizadas
  • Seguro para SSR — esta página lo demuestra
  • TypeScript-first — se exporta publicApiType
  • Una API estable de React 16.8 a 19

No está en la caja

  • Física de ajuste y de muelle
  • Sliders de imágenes a pantalla completa
  • Lightboxes

Eso pertenece al mundo de los sliders de imágenes — Embla y Swiper lo hacen bien. El bucle infinito y el autoplay tampoco son props: son recetas, de unas sesenta líneas de la API pública cada una, editables en vivo en Storybook. El carril cerca de la parte superior de esta página es exactamente esa receta en funcionamiento. Esto sigue siendo un menú.

Descargado 347.516 veces el mes pasado por unos 20.000 repositorios — mantenido desde 2018.

788 estrellas en GitHubDestacado en React Status #257En producción en Our World in Data

Cada ejemplo es editable, en tu navegador

Storybook hace las veces de patio de juegos: cada historia viene con un editor Monaco cargado con las definiciones de tipo reales de la biblioteca. Cambia el código, mira cómo se vuelve a renderizar — sin cuenta de sandbox ni configuración local.

Construido y mantenido por Aleksandr Smyshliaev

Publicado por primera vez en 2018, la misma API pública de React 16.8 a 19. Aleksandr es ingeniero frontend — React, Next.js, TypeScript — y actualmente está abierto a trabajo por contrato y a tiempo completo.