# react-horizontal-scrolling-menu: a scroll row that knows which items you can see

> This React component gives you a horizontally scrolling row of your own items and, more usefully, tells each item whether it is currently on screen, so an arrow can disable itself at the ends. It measures nothing and leaves styling to a separate CSS import, which is both its strength and the thing you have to build yourself.

**asmyshlyaev177/react-horizontal-scrolling-menu** — Horizontal scrolling menu component for React with per-item visibility tracking. Supports mouse and touch devices, SSR-safe.

- Repository: https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu
- Website: https://react-horizontal-scrolling-menu.dev/
- Stars: 789 · Forks: 102
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/asmyshlyaev177-react-horizontal-scrolling-menu

## Native scrolling, with visibility state on top

The component is a horizontal scrolling menu for React, built on native browser scrolling with per-item visibility tracking. The README positions it for category rows, tab strips, chip filters and galleries, that is, any row of things the application needs to reason about. The key word is native. This is not a transform-based carousel that moves a track with JavaScript; the browser does the scrolling, and the component's job is to observe and report. That is why the package describes navigation as working by scrollbar, touch, mouse wheel, drag, or the arrow components you provide. The second half of the design is that the items are your own components with your own CSS, and the menu is responsive to its parent width. It ships at 5.7 kB minified and gzipped, and the description says it is SSR-safe.

## Every item needs an id, and that is how visibility is tracked

The mechanism for knowing what is on screen is a context and an item id. The README states three things the example relies on. Every item needs a unique itemId prop, and that is how visibility tracking works, with the React key working as a fallback. The styles.css is a separate import, because the JavaScript bundle never injects CSS. And item width comes from your own CSS, because the menu measures nothing. That last point is the design decision worth pausing on. The component does not measure your items, so it does not need to know your layout, which is what makes it work inside a flex row, a grid or a responsive container without configuration. The cost is that you cannot ask the component how wide an item is, and the IntersectionObserver approach means the first paint may briefly show arrows in the wrong state before the observer settles.

## Arrows are your components, and they read the context

The scroll arrows are not part of the library. You pass LeftArrow and RightArrow as props to ScrollMenu, and each of them reads the VisibilityContext with useContext to decide whether to disable itself. The example shows both halves of that. The left arrow calls visibility.useIsVisible('first', true), treating the first item as visible by default so the arrow is disabled at the start, and the right arrow calls visibility.useIsVisible('last', false), treating the last item as not visible so the arrow is enabled. The same hook with a real item id gives you that item's visibility, and the example Card component uses it to set a data-visible attribute on its own div. The scroll actions are on the same context object, scrollPrev and scrollNext. Because the arrows are your components, they can be any element with any styling, and the README notes that plain JavaScript users can drop the type imports and use React.useContext(VisibilityContext) as usual.

## A complete example in twenty lines of your own code

The quick start example is short enough to read in full, which is the best evidence that the API is small. You import ScrollMenu, VisibilityContext and the publicApiType from the package, import the stylesheet, generate an array of item ids, and map them into a ScrollMenu with your two arrow components. Each arrow pulls the context, asks whether the first or last item is visible, and calls scrollPrev or scrollNext on click. The item component asks the same context about its own id and reflects the answer in a data-visible attribute on a div with a fixed width. The whole point of the library is in that one hook, and everything else in the example is your own code.

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

The two import paths for the stylesheet are both declared in the package exports, as ./dist/styles.css and ./styles.css, so either import works depending on your bundler.

## Installing the package, and the shadcn/ui variant

The package installs from npm:

```bash
npm install react-horizontal-scrolling-menu
```

If you are on shadcn/ui, the README says one command installs a styled, ready-made component, with edge-aware arrow buttons, drag-to-scroll and a hidden scrollbar, straight into your components/ui/ directory. The command is:

```bash
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/scroll-menu.json
```

The registry entry is the interesting part of the ecosystem design. A registry item is a JSON description of a component that the shadcn CLI copies into your project rather than a dependency you install, which means the arrow buttons and the hidden scrollbar become your code and you can edit them freely. The registry topic on the repository is listed as shadcn-registry, and the package keywords mention intersection-observer, which is the mechanism behind the visibility tracking. The base package and the registry version are two different things, and if you want the second one you should be clear which you are installing.

## Eight SKILL.md files because models keep using an older API

The AI agent section of the README is unusual and reveals a real problem. Models trained on older releases 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 prevent that, providing task-scoped guidance loaded on demand through TanStack Intent, versioned with the library rather than with any web page. The install is two commands:

```bash
npm install react-horizontal-scrolling-menu
npx @tanstack/intent@latest install   # once per project
```

The install step adds skill discovery to your agent's config, so CLAUDE.md or .cursorrules, and the agent then loads a skill on demand from node_modules/react-horizontal-scrolling-menu/skills/. The skills are named after tasks, with menu-setup covering a first working menu, the arrows and the required CSS import, menu-visibility covering what is on screen and the arrow state at the edges, and menu-scrolling covering the scroll behaviour. You can also list them or load one directly with npx @tanstack/intent@latest list and npx @tanstack/intent@latest load react-horizontal-scrolling-menu#menu-setup. This is a small idea with a real payoff, because a stale API in a model suggestion is exactly the failure mode that makes people give up on a library.

## Version 8 in the manifest, tags from 2021 in the release list

The versioning here needs a careful reading, and it is the first thing to check before you plan an upgrade. The package.json version is 8.3.2 with pnpm 11.0.9 as the package manager and the module type set to esm, and the exports map gives a browser and module entry at dist/index.mjs, a require and umd entry at dist/index.cjs and types at dist/types/index.d.ts. The listed GitHub releases, however, are v2.0.6, v2.6.0 and v2.7.0, all dated 2021-11-28, and the README notes that the last release was a scrollIntoView polyfill and that version 2.0.6 was the Version V2 release. The repository itself is not archived and the last push was on 2026-10-01, so work is clearly landing on the branch even though the release tags have not moved. The licence is MIT with the LICENSE file at the root, and the repository also carries a Storybook setup, an e2e directory with a Playwright config, example-nextjs and example-tanstack directories, and a website/ directory for the docs site.

## Conclusion

Adopt react-horizontal-scrolling-menu if you need a scrollable row of chips, tabs or cards where the arrow buttons should grey out at the ends, and you are willing to own the item styling. Do not adopt it if you want a batteries-included carousel, because the component supplies the scrolling and the visibility state and nothing else. Before you commit, check the version you are installing against the API the README documents, since older releases used different names, and remember that the stylesheet is a separate import that the JavaScript bundle never injects for you.

## FAQ

### What is react-horizontal-scrolling-menu?

It is an MIT licensed React component for a horizontally scrolling row of items, built on native browser scrolling with per-item visibility tracking, supporting mouse and touch devices and described as SSR-safe. It ships at 5.7 kB minified and gzipped.

### How do I install react-horizontal-scrolling-menu?

With npm install react-horizontal-scrolling-menu. If you use shadcn/ui, a single npx shadcn@latest add command installs a styled, ready-made version with edge-aware arrows, drag-to-scroll and a hidden scrollbar into your components/ui/ directory.

### How do I hide the scroll arrows at the ends of the menu?

You write your own arrow components and read the VisibilityContext with useContext. Calling visibility.useIsVisible('first', true) makes the left arrow disabled at the start, and useIsVisible('last', false) makes the right arrow enabled at the end, with scrollPrev and scrollNext on the same context doing the scrolling.

### Does react-horizontal-scrolling-menu include its own styles?

No. The stylesheet is a separate import, and the JavaScript bundle never injects CSS, so you import react-horizontal-scrolling-menu/dist/styles.css yourself. Item width also comes from your own CSS, because the menu measures nothing.

## Sources

- [asmyshlyaev177/react-horizontal-scrolling-menu on GitHub](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu)
- [License: MIT](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu/blob/master/LICENSE)
- [Project website](https://react-horizontal-scrolling-menu.dev/)
- [README](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu/blob/master/README.md)
- [Releases](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/asmyshlyaev177-react-horizontal-scrolling-menu
