Custom Shadcn Scrollspy for React and Tailwind CSS. Dynamically highlights navigation to indicate current visible section in viewport during page scroll
Browse 2 production-ready Shadcn Scrollspy components for dashboards, forms, and product UI. These examples follow the Radix UI implementation with accessible primitives from the Radix stack and stay fully compatible with Shadcn Create so radius, color, and typography match your configured theme.
Browse all 2 Shadcn Scrollspy components for copy-ready layouts, dashboards, and forms built with Tailwind CSS in the ReUI library.
import { Scrollspy } from "@/components/reui/scrollspy"<Scrollspy targetRef={containerRef}>
<a href="#section-1" data-scrollspy-anchor="section-1">Section 1</a>
<a href="#section-2" data-scrollspy-anchor="section-2">Section 2</a>
</Scrollspy>
<div ref={containerRef}>
<div id="section-1">Content 1</div>
<div id="section-2">Content 2</div>
</div>The main component that wraps the navigation links and manages the scroll spying logic.
Navigation links within Scrollspy should use these attributes to connect to sections:
The component adds data-active="true" to the link element when its corresponding section is active.
"use client"
import { useRef } from "react"
import { Scrollspy } from "@/components/reui/scrollspy"
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
export function Pattern() {
const parentRef = useRef<HTMLDivElement | null>(null)
const nav = [
{
id: "section-1",
label: "Section 1",
},
{
id: "section-2",
label: "Section 2",
},
{
id: "section-3",
label: "Section 3",
},
{
id: "section-4",
label: "Section 4",
},
{
id: "section-5",
label: "Section 5",
},
]
return (
<div className="flex w-full grow gap-5">
<div className="flex w-[150px] flex-col gap-2">
<Scrollspy
offset={50}
targetRef={parentRef}
className="flex flex-col gap-2.5"
>
{nav.map((item) => (
<Button
key={item.id}
variant="outline"
data-scrollspy-anchor={item.id}
className={
"data-[active=true]:bg-primary data-[active=true]:text-primary-foreground"
}
>
{item.label}
</Button>
))}
</Scrollspy>
</div>
<div className="grow" ref={parentRef}>
<ScrollArea className="-me-5 h-[500px] grow pe-5">
<div className="space-y-8">
{nav.map((item) => (
<div key={item.id} id={item.id} className="space-y-2.5">
<h3 className="text-foreground text-base">{item.label}</h3>
<div className="bg-muted rounded-2xl h-[350px]"></div>
</div>
))}
</div>
</ScrollArea>
</div>
</div>
)
}