The ScrollProgress component fills a thin bar as the reader moves through a page or a panel, scaling a single transform rather than resizing an element so it costs nothing on the scroll frame.
On anything long enough that the scrollbar alone does not answer 'how much is left', such as a blog post, changelog or terms page. It earns its place when the answer is not already obvious, so skip it on a short marketing page, where a bar that jumps to full after one flick just draws attention to how little there was. Pass `container` to track a scrollable panel and place the component inside a `relative` wrapper around that panel, not inside the panel itself; omit `container` and the bar fixes itself to the viewport and tracks the whole page.
Add it with the CLI, open it in v0, or copy the source into your project.
Everything Scroll Progress accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
| container | RefObject<HTMLElement | null> | No default | Scroll container to track. Omit to track the window, which also switches the bar from absolute to fixed positioning |
| height | number | 3 | Thickness of the bar, in pixels |
| color | string | var(--primary) | Any CSS colour or gradient |
| position | 'top' | 'bottom' | top | Which edge of the container the bar sits on |
| smooth | boolean | true | Ease the bar toward the true position with a spring instead of tracking raw scroll deltas |
| className | string | '' | Additional CSS classes for styling |
Motion is a language, and most interfaces speak it badly.
A transition that runs too long reads as lag. One that skips a frame reads as a bug.
The fix is to move only what actually changed, and to move it the shortest distance that still explains itself.
Everything else holds still, so the one thing that moved is the thing you notice.