Horizontal scrolling menu component for React.
Horizontal scrolling menu component for React.
English · 简体中文 · 日本語 · 한국어 · Русский · Español · Português (BR) · Français · Tiếng Việt
A horizontal scrolling menu component for React, built on native browser scrolling with per-item visibility tracking. Good for category rows, tab strips, chip filters, galleries — any row of things your app needs to reason about. Items are your own components with your own CSS; the menu is responsive to its parent width; navigation works by scrollbar, touch, mouse wheel, drag, or the arrow components you provide. 5.7 kB min+gzip.
Over 20,000 repositories depend on this library. Five you can go and read —
every link lands on the import in the component that uses it, pinned to a
commit, not on a package.json:
ToggleButton. ^8.2.0VerticalList in their shared component package, built from this library's
own docs. ^8.2.0^4.0.4^3.2.5Tabs component; their
Arrows
use VisibilityContext directly. Archived in 2024, pins ^2.1.1.Also featured in React Status #257.
npm install react-horizontal-scrolling-menu
Using shadcn/ui? One command installs a styled,
ready-made component — edge-aware arrow buttons, drag-to-scroll, hidden
scrollbar — straight into your components/ui/:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/scroll-menu.json
…
Three things the example relies on:
itemId prop — that's how visibility tracking
works. The React key works as a fallback.styles.css is a separate import; the JS bundle never injects CSS.Writing plain JavaScript? Drop the type imports and use
React.useContext(VisibilityContext) as usual.
Models trained on older releases still reach for visibleElements,
Separator items and an Arrows prop — all removed — and invent an
autoplay prop that never existed. The package ships eight SKILL.md
files to stop that: task-scoped guidance loaded on demand through
TanStack Intent,
versioned with the library rather than with any web page.
npm install react-horizontal-scrolling-menu
npx @tanstack/intent@latest install # once per project
install adds skill discovery to your agent's config (CLAUDE.md,
.cursorrules, …); the agent then loads a skill on demand from
node_modules/react-horizontal-scrolling-menu/skills/. List or load them
directly with npx @tanstack/intent@latest list and
npx @tanstack/intent@latest load react-horizontal-scrolling-menu#menu-setup.
| Skill | When it's loaded |
|---|---|
menu-setup |
A first working menu, arrows, the required CSS import |
menu-visibility |
What's on screen, and arrow state at the edges |
menu-scrolling |
scrollToItem, apiRef, page-at-a-time paging |
menu-interactions |
Drag, wheel and touch — and their handler factories |
menu-recipes |
Autoplay, infinite loop, load-more: recipes, not props |
menu-transitions-rtl |
Animation timing, custom easing, right-to-left |
menu-testing-ssr |
Next.js and RSC, Jest mocks, Playwright |
menu-migration |
Upgrading pre-v8 code, and the APIs models still invent |
The sources live in skills/. Agents that can't load Intent
skills should read
llms.txt instead —
the same facts, condensed into one file.
Built on native browser scrolling: momentum, scrollbar, touch, wheel and
accessibility come from the browser, not a physics reimplementation. On top
of that: per-item visibility via IntersectionObserver, scrollToItem /
scrollNext / scrollPrev, an apiRef for control from outside, Header
and Footer slots, RTL, dynamic add/remove detection, and TypeScript types
throughout. SSR-safe — the landing page
server-renders every demo.
No snap or spring physics built in — slide effects (fade, cube, coverflow) are the one job better served by a dedicated effects library, and trying one costs minutes these days. The comparison page lays the trade-offs out honestly, with deep dives on Embla vs Swiper, react-slick alternatives and Swiper alternatives. Autoplay and infinite loop aren't props either; they're recipes of about sixty lines each on the public API, live-editable in Storybook (infinite loop, autoplay). If you need a row that knows what's visible, this is it.
Complete patterns by outcome, each with a live server-rendered demo, the code, and a matching shadcn install: Netflix-style row · scrollable tabs · filter chips · category rail.
Every example is live-editable in the Storybook — each story ships with a Monaco editor loaded with the library's real type definitions. Covers: basic usage, one-item-per-scroll, mouse drag, scroll to item on mount, center on click, adding items dynamically, save/restore position, items animation, progress dots, preventing body scroll, custom transitions, infinite loop, autoplay, vertical layout, arrows in the footer, mobile swipe, RTL, and a 5000-item stress test.
Children of the main ScrollMenu component (arrows, header, footer, items)
can use VisibilityContext to access state and callbacks. Function
callbacks also receive the context, e.g. onWheel, onScroll.
| Prop | Signature |
|---|---|
| LeftArrow | React component for left arrow |
| RightArrow | React component for right arrow |
| Header | React component Header |
| Footer | React component Footer |
| onWheel | (VisibilityContext, event) => void |
| onScroll | (VisibilityContext, event) => void, fires before scroll settles |
| onInit | (VisibilityContext) => void |
| onUpdate | (VisibilityContext) => void |
| apiRef | React.RefObject | React.RefCallback |
| options | options for IntersectionObserver - rootMargin, threshold, and ratio to consider element visible |
| containerRef | React.RefObject | React.RefCallback for the scroll container |
| onMouseDown | (VisibilityContext) => (React.MouseEventHandler) => void |
| onMouseLeave | (VisibilityContext) => (React.MouseEventHandler) => void |
| onMouseUp | (VisibilityContext) => (React.MouseEventHandler) => void |
| onMouseMove | (VisibilityContext) => (React.MouseEventHandler) => void |
| onTouchMove | (VisibilityContext) => (React.TouchEventHandler) => void |
| onTouchStart | (VisibilityContext) => (React.TouchEventHandler) => void |
| onTouchEnd | (VisibilityContext) => (React.TouchEventHandler) => void |
| itemClassName | ClassName of Item |
| scrollContainerClassName | ClassName of scrollContainer |
| wrapperClassName | ClassName of the outer-most div |
| transitionDuration | Duration of transitions in ms, default 500, needs noPolyfill={false} |
| transitionBehavior | 'smooth' | 'auto' | custom function, needs noPolyfill={false} |
| RTL | Enable Right to left direction |
| noPolyfill | true by default (native scrollIntoView); set false to enable transition props |
Note the two callback shapes: onWheel and onScroll are
No open issues yet, or sync has not completed.