Scroll Progress

A scroll progress pill that tracks reading position and expands into a squircle menu of sections you can jump to.

Dependencies

motion

Interaction Type

Scroll to fill the ring and watch the active section label crossfade in. Click the pill to morph it into a squircle menu, then tap any section to smooth-scroll there. Click outside or press Escape to close.

Props

Options you can pass to customize this component.

Prop
Type
Description
sections
Array<{ id: string; label: string }>

Ordered sections shown as the reader moves and listed in the menu. Each id must match an element id present in the scrolled content.

containerRef
React.RefObject<HTMLElement | null>

Scroll container to track and scroll within. Defaults to the window when omitted.

offset
number

Distance in pixels below the scroller's top edge that a section must cross to be marked active.

className
string

Extra classes merged onto the fixed root wrapper — use it to reposition the pill.

Installation

npx shadcn add swamimalode07/rare-ui/scroll-progress

How to use

"use client"
import { useRef } from "react"
import ScrollProgress from "@/components/ui/scroll-progress"
const sections = [
{ id: "intro", label: "Introduction" },
{ id: "usage", label: "Usage" },
{ id: "faq", label: "FAQ" },
]
export function Demo() {
const scrollRef = useRef<HTMLElement>(null)
return (
<main ref={scrollRef} className="relative h-full overflow-auto">
<ScrollProgress containerRef={scrollRef} sections={sections} />
<section id="intro">{/* ... */}</section>
<section id="usage">{/* ... */}</section>
<section id="faq">{/* ... */}</section>
</main>
)
}
// Tracks the window with no container ref:
// <ScrollProgress sections={sections} />

Source Code

Click the code icon in the top-right corner to view the source code.

Keep in mind

Most components here are recreations of great work from around the web. I don't claim to be the original creator - this is my attempt to reverse-engineer, replicate, and often add a few extra features. I've tried to credit everyone; if I missed someone, let me know.

Contact

Found a bug or issue? Feel free to drop a DM.

License & Usage

  • Free to use and modify in both personal and commercial projects.
  • Attribution to Rare UI is appreciated when using a component.
  • Please don't resell the components as your own kit.
Scroll Progress

Introduction

Scroll Progress

A thin indicator that reports how far the reader has moved through a page or a scroll container. It sits quietly at the edge of the viewport and fills as the content advances.

Why it exists

A sense of position on long pages

Long articles hide their own length. A progress bar gives readers a continuous cue for how much remains, without adding another block of chrome to the layout.

The signal

One value, mapped to width

The component tracks a single scalar between zero and one and maps it to the fill. Everything else — color, thickness, placement — is presentation on top of that value.

Placement

Pinned to the top by default

The indicator is fixed to the top edge so it stays visible while the content scrolls beneath it. It can also be attached to the bottom or to a specific container.

Interaction

Behavior

The bar follows scroll position directly. It should feel like a physical readout of the page rather than an animation that plays on its own.

Tracking

Progress is derived, not stored

Scroll offset divided by the total scrollable distance gives the progress value. Because it is derived on every frame, it stays correct through resizes and content changes.

Smoothing

A spring keeps the fill from stuttering

Raw scroll values can arrive in jumps. Passing the value through a spring smooths the fill so it glides toward the target instead of snapping between frames.

Performance

Animate transform, never layout

The fill is driven by scaleX with a left transform origin. Scaling avoids layout work on every scroll event and keeps the indicator smooth on long documents.

Visual System

Styling

The default look is deliberately minimal: a few pixels tall, full width, and tied to the foreground color so it reads clearly in both themes.

Thickness

Keep it thin

Two to four pixels is enough to register in peripheral vision. A heavier bar competes with the content and starts to feel like a loading state rather than a position cue.

Contrast

Lean on the foreground token

Using the foreground color keeps the bar legible without a hardcoded value, so it inverts correctly when the theme flips between light and dark.

Direction

Fill from the leading edge

A left transform origin makes the bar grow in the reading direction. For right-to-left layouts the origin flips so the motion still matches the flow of text.

Implementation

Usage

Drop the component near the top of a scrollable layout and it works with no configuration. Everything past that is optional refinement.

Mounting

One instance per scroll surface

Render a single indicator for the page. If you have an independent inner scroll area, give that region its own instance pointed at the container.

Layout

Use className for placement and color

The component owns the tracking logic; the surrounding page controls where the bar sits and how it is themed through a className.

Accessibility

Respect reduced motion

The progress bar is decorative, so it should not be the only indicator of position. When reduced motion is requested, drop the spring and track scroll directly.