Horizontal Scroll Gallery for React

Pinned section where vertical scroll drives horizontal gallery position. Same pattern as Scroll-Linked Video Scrubber.

Component

1
2
3
4
5
6
7
8

Installation

 

Usage

Import

Add the import.

import { useRef } from "react";
import { HorizontalScrollGallery } from "@/components/horizontal-scroll-gallery";

Use

Wrap gallery items. Pass scroller when inside a scroll container.

const containerRef = useRef<HTMLDivElement>(null);

<div ref={containerRef} className="h-96 overflow-y-auto">
  <HorizontalScrollGallery scroller={containerRef}>
    {items.map((item) => (
      <Card key={item.id} {...item} />
    ))}
  </HorizontalScrollGallery>
</div>;

Guidelines

  • Pass gallery items as children. They are laid out in a horizontal row.
  • When inside a scrollable container, pass scroller={containerRef}.
  • scrollHeight controls how much vertical scroll is needed to traverse the gallery.

Props

All props are optional unless marked required. Use these to customize every aspect of the component.

PropTypeDefaultDescription
childrenReact.ReactNode-Gallery items (cards, images). Rendered in a horizontal row.
classNamestring-Additional CSS classes for the wrapper.
scrollerReact.RefObject<HTMLElement | null>-Ref to the scroll container. Pass when inside overflow-y-auto.
scrollHeightstring"300%"Height of the vertical scroll runway. Larger = slower horizontal advance.

Accessibility

  • The wrapper and track are presentational only; the component adds no interactive controls, ARIA roles, labels, or focusable elements of its own.
  • Any keyboard operability and focus visibility depend entirely on the children you pass in, so links, buttons, and cards should remain natively focusable and reachable by tab order.
  • Because horizontal position is driven by vertical scroll, keyboard-only users can still advance the gallery by scrolling the container, but there is no arrow-key or button affordance to step between items.
  • The component does not check prefers-reduced-motion, so it should be guarded or disabled for users who request reduced motion since the scroll-linked horizontal travel can be disorienting.

Performance

  • Only the CSS transform translateX is animated, which is GPU-composited and avoids layout or paint, making each update cheap.
  • The track carries will-change transform to hint the browser to promote it to its own compositor layer.
  • Scroll updates write the transform synchronously in a scroll handler registered with passive true, so they never block scrolling, though there is no requestAnimationFrame batching to coalesce bursts of events.
  • A ResizeObserver on the track and another on the scroll container recompute sizing, and both observers plus the scroll listener are disconnected in effect cleanup to prevent leaks.
  • There is no IntersectionObserver or visibility gating, so the handler keeps running while scrolling even when the gallery is off screen.

Examples

Basic

Numbered cards. Scroll vertically to move horizontally through the gallery.

1
2
3
4
5
6
7
8
import { useRef } from "react";
import { HorizontalScrollGallery } from "@/components/horizontal-scroll-gallery";

export function GalleryDemo() {
  const containerRef = useRef<HTMLDivElement>(null);
  return (
    <div
      ref={containerRef}
      className="h-72 overflow-y-auto"
    >
      <HorizontalScrollGallery
        scroller={containerRef}
        scrollHeight="400%"
      >
        {[1, 2, 3, 4, 5, 6].map((i) => (
          <div
            key={i}
            className="flex h-40 w-56 shrink-0 items-center justify-center rounded-lg border border-border bg-muted text-2xl font-bold"
          >
            {i}
          </div>
        ))}
      </HorizontalScrollGallery>
    </div>
  );
}

Last updated on Jun 19

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: