Horizontal Scroll Row

Horizontal scroll container with snap scrolling, chevron navigation, and hidden scrollbar.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/horizontal-scroll-row.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

2 stories available:

Code

"use client"; import { memo, type ReactNode } from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; import type { HeadingTag } from "../../lib/types"; import { useHorizontalScroll } from "../../lib/use-horizontal-scroll"; import { cn } from "../../lib/utils"; import { Button } from "../button/button"; type HorizontalScrollRowProps = { /** Heading tag for the title. Defaults to `h3`. */ as?: HeadingTag; children: ReactNode; className?: string; description?: string; title: string; }; const HorizontalScrollRow = memo(function HorizontalScrollRow({ as: Heading = "h3", children, className, description, title, }: HorizontalScrollRowProps) { const { canScrollLeft, canScrollRight, containerRef, scroll } = useHorizontalScroll(); const showControls = canScrollLeft || canScrollRight; return ( <section className={cn("space-y-4", className)}> <div className="flex items-center justify-between"> <div> <Heading className="text-lg font-semibold">{title}</Heading> {description ? ( <p className="text-sm text-muted-foreground">{description}</p> ) : null} </div> {showControls ? ( <div className="flex gap-1"> <Button aria-label="Scroll left" className="size-8" disabled={!canScrollLeft} onClick={() => { scroll("left"); }} size="icon" variant="outline" > <ChevronLeft className="size-4" /> </Button> <Button aria-label="Scroll right" className="size-8" disabled={!canScrollRight} onClick={() => { scroll("right"); }} size="icon" variant="outline" > <ChevronRight className="size-4" /> </Button> </div> ) : null} </div> <div className="flex gap-4 overflow-x-auto snap-x snap-mandatory [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" ref={containerRef} > {children} </div> </section> ); }); HorizontalScrollRow.displayName = "HorizontalScrollRow"; export { HorizontalScrollRow }; export type { HorizontalScrollRowProps };

Dependencies

  • @vllnt/ui@^0.2.1
  • lucide-react