Scroll Progress Rail
Fixed vertical scroll progress rail for long marketing and docs pages. Page-only fill or optional section markers with click-to-jump, scoped progress via scopeRef, ScrollSectionAnchor registration, and reduced-motion fallbacks.
Page progress
Page-only rail that fills as the document scrolls.
Intro
Scroll the page to watch the rail fill (1 of 5).
Page mode tracks overall document scroll without section markers. The rail stays fixed on the viewport while you move through this example.
Features
Scroll the page to watch the rail fill (2 of 5).
Page mode tracks overall document scroll without section markers. The rail stays fixed on the viewport while you move through this example.
Workflow
Scroll the page to watch the rail fill (3 of 5).
Page mode tracks overall document scroll without section markers. The rail stays fixed on the viewport while you move through this example.
Pricing
Scroll the page to watch the rail fill (4 of 5).
Page mode tracks overall document scroll without section markers. The rail stays fixed on the viewport while you move through this example.
FAQ
Scroll the page to watch the rail fill (5 of 5).
Page mode tracks overall document scroll without section markers. The rail stays fixed on the viewport while you move through this example.
page-example.tsx
1"use client";23import { useRef } from "react";4import {5 Card,6 CardContent,7 CardDescription,8 CardHeader,9 CardTitle,10} from "@/components/ui/card";11import { ScrollProgressRail } from "@/registry/ui";1213const PAGE_BLOCKS = [14 "Intro",15 "Features",16 "Workflow",17 "Pricing",18 "FAQ",19] as const;2021export function ScrollProgressRailPageExample() {22 const scopeRef = useRef<HTMLDivElement>(null);2324 return (25 <>26 <ScrollProgressRail scopeRef={scopeRef} side="right" />2728 <div29 ref={scopeRef}30 className="w-full rounded-xl border bg-linear-to-b from-muted/20 to-background"31 >32 <div className="flex flex-col gap-8 p-6 sm:p-8">33 {PAGE_BLOCKS.map((block, index) => (34 <Card key={block} className="min-h-56 border-border/70 bg-card/90">35 <CardHeader>36 <CardTitle>{block}</CardTitle>37 <CardDescription>38 Scroll the page to watch the rail fill ({index + 1} of{" "}39 {PAGE_BLOCKS.length}).40 </CardDescription>41 </CardHeader>42 <CardContent className="text-sm text-muted-foreground">43 Page mode tracks overall document scroll without section44 markers. The rail stays fixed on the viewport while you move45 through this example.46 </CardContent>47 </Card>48 ))}49 </div>50 </div>51 </>52 );53}
Section markers
Anchored sections with labeled markers and Reveal blocks.
Scroll-aware navigation
A fixed rail that tracks page progress at a glance.
A fixed rail that tracks page progress at a glance.
Section markers
Optional dots align to anchored sections and support click-to-jump.
Optional dots align to anchored sections and support click-to-jump.
Long-form pages
Pair with reveal animations on landing and docs layouts.
Pair with reveal animations on landing and docs layouts.
Accessible by default
Progressbar semantics in page mode and labeled section controls.
Progressbar semantics in page mode and labeled section controls.
Composable backdrop
Works alongside layered wave footers and hero sections.
Works alongside layered wave footers and hero sections.
sections-example.tsx
1"use client";23import { useRef } from "react";4import {5 Card,6 CardContent,7 CardDescription,8 CardHeader,9 CardTitle,10} from "@/components/ui/card";11import {12 Reveal,13 ScrollProgressRail,14 ScrollSectionAnchor,15 ScrollSectionRailProvider,16} from "@/registry/ui";1718const SECTION_ITEMS = [19 {20 id: "spr-overview",21 label: "Overview",22 title: "Scroll-aware navigation",23 description: "A fixed rail that tracks page progress at a glance.",24 },25 {26 id: "spr-sections",27 label: "Sections",28 title: "Section markers",29 description:30 "Optional dots align to anchored sections and support click-to-jump.",31 },32 {33 id: "spr-marketing",34 label: "Marketing",35 title: "Long-form pages",36 description: "Pair with reveal animations on landing and docs layouts.",37 },38 {39 id: "spr-accessibility",40 label: "Accessibility",41 title: "Accessible by default",42 description:43 "Progressbar semantics in page mode and labeled section controls.",44 },45 {46 id: "spr-compose",47 label: "Compose",48 title: "Composable backdrop",49 description: "Works alongside layered wave footers and hero sections.",50 },51] as const;5253export function ScrollProgressRailSectionsExample() {54 const scopeRef = useRef<HTMLDivElement>(null);55 const overviewRef = useRef<HTMLElement>(null);5657 return (58 <ScrollSectionRailProvider>59 <ScrollProgressRail60 scopeRef={scopeRef}61 side="right"62 sections={[63 { id: "spr-overview", label: "Overview", ref: overviewRef },64 ...SECTION_ITEMS.slice(1).map(({ id, label }) => ({ id, label })),65 ]}66 />6768 <div ref={scopeRef} className="w-full rounded-xl border bg-background">69 <div className="flex flex-col gap-16 p-6 sm:p-8">70 {SECTION_ITEMS.map((section, index) => {71 const content = (72 <Reveal animation="fade-up">73 <Card className="min-h-52 border-border/70 bg-card/95">74 <CardHeader>75 <CardTitle>{section.title}</CardTitle>76 <CardDescription>{section.description}</CardDescription>77 </CardHeader>78 <CardContent className="text-sm text-muted-foreground">79 {section.description}80 </CardContent>81 </Card>82 </Reveal>83 );8485 if (index === 0) {86 return (87 <ScrollSectionAnchor88 key={section.id}89 sectionId={section.id}90 ref={overviewRef}91 className="scroll-mt-24"92 >93 {content}94 </ScrollSectionAnchor>95 );96 }9798 return (99 <ScrollSectionAnchor100 key={section.id}101 sectionId={section.id}102 className="scroll-mt-24"103 >104 {content}105 </ScrollSectionAnchor>106 );107 })}108 </div>109 </div>110 </ScrollSectionRailProvider>111 );112}
Marketing page
Long-form layout with section rail, Reveal storytelling, and LayeredWaves footer.
Scroll progress rail
Long pages, clear orientation
Section markers on the left track hero, features, and proof blocks while readers move through the story.
Reveal as you scroll
Combine section markers with staggered reveal blocks for polished storytelling.
Built for docs and launches
Use the same primitive on changelog pages, product tours, and launch sites.
Layered waves finish the page while the rail keeps section context visible above the motion layer.
marketing-example.tsx
1"use client";23import { useRef } from "react";4import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";5import {6 Reveal,7 ScrollProgressRail,8 ScrollSectionAnchor,9 ScrollSectionRailProvider,10} from "@/registry/ui";1112const MARKETING_SECTIONS = [13 {14 id: "spr-hero",15 label: "Hero",16 title: "Ship pages that feel alive",17 body: "Guide readers through long marketing surfaces with a subtle progress rail.",18 },19 {20 id: "spr-features",21 label: "Features",22 title: "Reveal as you scroll",23 body: "Combine section markers with staggered reveal blocks for polished storytelling.",24 },25 {26 id: "spr-proof",27 label: "Proof",28 title: "Built for docs and launches",29 body: "Use the same primitive on changelog pages, product tours, and launch sites.",30 },31] as const;3233export function ScrollProgressRailMarketingExample() {34 const scopeRef = useRef<HTMLDivElement>(null);35 const heroRef = useRef<HTMLElement>(null);3637 return (38 <ScrollSectionRailProvider>39 <ScrollProgressRail40 scopeRef={scopeRef}41 side="left"42 sections={[43 { id: "spr-hero", label: "Hero", ref: heroRef },44 ...MARKETING_SECTIONS.slice(1).map(({ id, label }) => ({45 id,46 label,47 })),48 ]}49 />5051 <div52 ref={scopeRef}53 className="w-full overflow-hidden rounded-xl border bg-background"54 >55 <div className="flex flex-col">56 <ScrollSectionAnchor57 sectionId="spr-hero"58 ref={heroRef}59 className="scroll-mt-24 px-6 py-16 sm:px-10 sm:py-20"60 >61 <Reveal animation="blur-in">62 <div className="mx-auto max-w-2xl space-y-4 text-center">63 <p className="text-sm font-medium uppercase tracking-[0.2em] text-primary">64 Scroll progress rail65 </p>66 <h2 className="text-3xl font-semibold tracking-tight sm:text-4xl">67 Long pages, clear orientation68 </h2>69 <p className="text-muted-foreground text-base sm:text-lg">70 Section markers on the left track hero, features, and proof71 blocks while readers move through the story.72 </p>73 </div>74 </Reveal>75 </ScrollSectionAnchor>7677 {MARKETING_SECTIONS.slice(1).map((section) => (78 <ScrollSectionAnchor79 key={section.id}80 sectionId={section.id}81 className="scroll-mt-24 px-6 py-12 sm:px-10"82 >83 <Reveal animation="fade-up">84 <Card className="mx-auto min-h-48 max-w-3xl border-border/70 bg-card/95">85 <CardHeader>86 <CardTitle>{section.title}</CardTitle>87 </CardHeader>88 <CardContent className="text-muted-foreground">89 {section.body}90 </CardContent>91 </Card>92 </Reveal>93 </ScrollSectionAnchor>94 ))}9596 <div className="mt-8 min-h-56">97 <div className="flex min-h-56 items-center justify-center px-6 py-12 text-center">98 <p className="max-w-lg text-sm text-muted-foreground sm:text-base">99 Layered waves finish the page while the rail keeps section100 context visible above the motion layer.101 </p>102 </div>103 </div>104 </div>105 </div>106 </ScrollSectionRailProvider>107 );108}
Installation & source
Install via the shadcn CLI or copy the registry files manually.
bash
npx shadcn@latest add @tt-ui/scroll-progress-rail
Props
| Name | Type | Default | Description |
|---|---|---|---|
| sections | ScrollProgressRailSection[] | - | Optional section markers; omit for page-only progress. Each entry accepts an optional ref or ScrollSectionAnchor registration |
| onSectionChange | (id: string) => void | - | Called when the active section marker changes |
| activeThreshold | number | 0.4 | Intersection ratio threshold for active section selection |
| side | "left" | "right" | "right" | Which viewport edge the rail sits on |
| fixed | boolean | true | Pin the rail to the viewport edge |
| scopeRef | RefObject<HTMLElement | null> | - | Scope scroll progress to this element (0→1 while it scrolls through the viewport) |
| embedded | boolean | false | Static rail for docs previews; skips Motion scroll tracking |
| className | string | - | Additional classes on the rail wrapper |