Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Navigation

Scroll Progress

Navigation

Scroll Progress React component for Tailwind CSS v4

Proximity SidebarSidebar
Live props
Behavior
offset120px
Appearance
size
position
inset24px
showLabel
accentColor
trackColor
surfaceColor
textColor
radius26px
Content
triggerLabel
currentLabel
listLabel
Code
import ScrollProgress from "@/components/ui/scroll-progress"

<ScrollProgress />
  1. Home
  2. Components
  3. Navigation
  4. Scroll Progress
Free

Scroll Progress React component for Tailwind CSS v4

v1.0.1·Updated in 1.2.1

A React scroll progress pill with a reading ring that opens into a menu of sections.

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. From the keyboard, the menu opens with focus on the current section, the arrow keys, Home and End move between sections, and Escape closes it and hands focus back to the pill. Click outside to close it too.

Scroll Progress is a free React component in the React navigation components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add scroll-progress, together with Motion presets and UIProvider and the npm package motion.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add scroll-progress

Usage

Import ScrollProgress from @/components/ui/scroll-progress.

"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}
        className="absolute"
        position="bottom-center"
      />

      <section id="intro">{/* ... */}</section>
      <section id="usage">{/* ... */}</section>
      <section id="faq">{/* ... */}</section>
    </main>
  )
}

// tracks the window with no container ref:
// <ScrollProgress sections={sections} />

Props

  • sectionsdefault "[]"
    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. Defaults to an empty list, which leaves just the ring.

  • offsetdefault 120
    number

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

  • sizedefault "md"
    "sm" | "md" | "lg"

    Scale of the pill, the ring and the menu rows. On touch screens the pill and every row grow to at least 44px tall so they are easy to hit.

  • positiondefault "bottom-center"
    "bottom-center" | "bottom-left" | "bottom-right" | "top-center" | "top-left" | "top-right"

    Where the pill is pinned. The menu grows away from that edge, and the safe-area insets of notched phones are added on top of inset.

  • insetdefault 24
    number

    Distance in pixels between the pill and the edges it is pinned to.

  • showLabeldefault true
    boolean

    Shows the active section name next to the ring. Turn it off for a compact round button with just the ring.

  • accentColordefault "auto"
    string

    Color of the progress ring and the active section dot, any CSS color. Auto uses the foreground color of the theme.

  • trackColordefault "auto"
    string

    Color of the ring track behind the progress, any CSS color. Auto is the foreground color at 15% opacity.

  • surfaceColordefault "auto"
    string

    Any CSS color for the pill and the open menu. Auto is the page background at 70% over a blur.

  • textColordefault "auto"
    string

    Any CSS color for the labels, the dots, the row highlight and the ring (when accentColor and trackColor are auto). Auto is the foreground color.

  • radiusdefault 26
    number

    Corner radius of the open menu in pixels. The rows follow it with a radius 12px smaller, and the closed pill is always fully round.

  • triggerLabeldefault "Show sections"
    string

    Accessible name of the pill button. Screen readers hear it followed by the current section.

  • currentLabeldefault "current"
    string

    The word between triggerLabel and the section name in the pill's accessible name: "Show sections, current: Pricing".

  • listLabeldefault "Sections"
    string

    Accessible name of the open menu.

  • containerRef
    React.RefObject<HTMLElement | null>

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

  • className
    string

    Extra classes merged onto the root wrapper, which is fixed to the viewport at the chosen position. Pass absolute to pin it inside a relative panel instead. Any other <div> attribute is passed through too.

Parts

  • ScrollProgressSection

    type

    One entry of sections: { id, label }. id is the element id the menu scrolls to and the scrollspy watches.

Examples

Long-form article

The small pill in the bottom right with a blue ring; tap it to jump between the article's sections.

Ring only

showLabel off keeps a compact round button with just the progress ring, here at the top right.

Product page

Features, pricing and FAQ with an orange ring, centered at the top of the card.

Dependencies

motionmotionui-config

Files

  • components/ui/scroll-progress.tsx

FAQ

Is the React Scroll Progress component free?

Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.

How do I install Scroll Progress?

Run npx wingo-ui@latest add scroll-progress. It also adds the Wingo UI items it builds on (Motion presets and UIProvider) and the npm package motion. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/scroll-progress.json.

See also

Proximity SidebarTable Of ContentsProgressBack To TopuseScrollStatehook

More React navigation components

Bounce SidebarHook SidebarGooey NavUser MenuNav MenuSidebar
All 19 React navigation components
PreviousProximity SidebarNext Sidebar
Scroll Progress
Scroll Progress
Behavior
Styling
Usage

Introduction

Scroll Progress

A small pill that reports how far the reader has moved through a page or a scroll container. A ring fills as the content advances, and the label names the section you are in.

Why it exists

A sense of position on long pages

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

The signal

One value, mapped to a ring

The component tracks a single scalar between zero and one and maps it to the stroke of the ring. The label, the menu and the placement are presentation on top of that value.

Placement

Pinned to the bottom center

The pill sits at the bottom of the viewport, within reach of the thumb, while the content scrolls beneath it. Pass className to move it, or point it at a specific scroll container.

Interaction

Behavior

The ring follows scroll position directly and the label swaps as each section crosses the offset line. It should feel like a 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, so it stays correct through resizes and content changes.

Smoothing

A spring keeps the ring from stuttering

Raw scroll values can arrive in jumps. Passing the value through a spring lets the ring glide toward its target instead of snapping between frames.

Jumping

The pill opens into a menu

Tap the pill and it morphs into a squircle list of every section. Pick one to smooth-scroll there, or press Escape or tap outside to close it again.

Visual System

Styling

The default look is deliberately quiet: a frosted pill with a thin border, tied to the background and foreground tokens so it reads clearly in both themes.

Surface

Frosted, not solid

A translucent background with a backdrop blur keeps the text underneath faintly visible, so the pill feels like part of the page instead of a sticker on top of it.

Contrast

Lean on the theme tokens

The ring, the label and the menu all use the foreground color, so everything inverts correctly when the theme flips between light and dark.

Motion

Sizes spring, labels crossfade

The surface springs between the pill and the menu sizes, while labels crossfade through a light blur so a change of section never feels abrupt.

Implementation

Usage

Drop the component into a scrollable layout and pass the sections you want to name. 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 it its own instance and pass that element as containerRef.

Offset

Tune when a section becomes active

The offset sets how far below the top edge a heading has to travel before its label takes over. Raise it for pages with a tall sticky header.

Accessibility

Respect reduced motion

When reduced motion is requested, the surface stops springing, the blur fades are dropped, and jumping to a section scrolls instantly.