Horizontal Scroll Section
A pinned section that turns vertical scrolling into horizontal movement, with built-in spring feel, a progress bar, and edge fading.
This is a WebberUI Pro component
Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.
npx shadcn@latest add "https://webberui.com/r/horizontal-scroll-section.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<HorizontalScrollSection />
Installation
npx shadcn@latest add "https://webberui.com/r/horizontal-scroll-section.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/horizontal-scroll-section.
Usage
The outer section automatically stretches out enough vertical scroll height for its content; as the user scrolls down, the inner track is pinned at the center of the screen and shifts to the left. Each direct child is one horizontal panel.
import { HorizontalScrollSection } from "@/components/ui/horizontal-scroll-section";
<HorizontalScrollSection gap={24} showProgress>
{items.map((item) => (
<div key={item.id} className="h-80 w-96 rounded-xl border p-6">
{item.content}
</div>
))}
</HorizontalScrollSection>;If the section sits inside a nested overflow-y-auto container, pass that container's ref to container and the component will use it as the reference for pinning and scrolling:
const scrollerRef = React.useRef<HTMLDivElement>(null);
<div ref={scrollerRef} className="h-[80vh] overflow-y-auto">
<HorizontalScrollSection container={scrollerRef}>
{/* Panels */}
</HorizontalScrollSection>
</div>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | Panels laid out horizontally in order; each direct child is one panel |
container | React.RefObject<HTMLElement | null> | — | Ref of the nested scroll container; defaults to using the window as the reference |
gap | number | 16 | Horizontal spacing between panels (px) |
smooth | boolean | true | Smooth the displacement with a spring, giving it damped momentum; turn it off for 1:1 tracking with the scroll |
stiffness | number | 90 | Spring stiffness (applies when smooth is true) |
damping | number | 26 | Spring damping (applies when smooth is true) |
showProgress | boolean | false | Show a horizontal progress bar at the bottom of the pinned area |
fadeEdges | boolean | false | Fade out the left and right edges with a mask, hinting that there is more content |
ariaLabel | string | "Horizontal scroll section" | Accessible label applied to the scroll section |
className | string | — | The outer section (which determines the total scroll height) |
viewportClassName | string | — | The pinned viewport container |
trackClassName | string | — | The horizontal track |
How it works
- Automatic measurement: a
ResizeObservermeasures the track'sscrollWidthand the viewport width, derives the vertical scroll distance from "track width − viewport width", and stretches the outer section's height accordingly. It recalculates automatically whenever the content, the window, or the container changes size. - Pinned displacement: the inner viewport is pinned with
sticky top-0;useScrollreads the section's progress anduseTransformmaps it into a horizontal displacementx. Withsmoothon, that value additionally passes throughuseSpringfor a soft, momentum-like settle. - Correct on the first frame: measurement runs synchronously in a layout effect, so the very first frame already carries the correct height and the layout never jumps. All timers,
ResizeObserverinstances, and event listeners are fully cleaned up on unmount. - Tuning:
stiffness/dampingcontrol the feel andgapcontrols panel spacing; panel width and height are yours to set on the children (the example usesw-56 h-40).
Accessibility
- When the user has "reduce motion" enabled at the system level, it switches to native
overflow-x-autohorizontal scrolling: the content stays fully accessible and no long stretch of vertical scroll is consumed. - The scroll section carries
role="region"and a customizablearia-label. - When keyboard Tab moves focus to a panel that is currently outside the viewport, the component converts that into the matching vertical scroll position and smoothly brings the panel into view, so focus never disappears off screen.
Parallax Section
A multi-layer parallax section where each layer shifts at a different speed with scroll progress, stacking up depth and a sense of space.
Sticky Reveal
A sticky text-and-visual reveal: the text on the left flows with the scroll while the visual on the right stays pinned and cross-fades between states.