Scroll Progress
A one-element progress bar. The component renders a single block and sets its
scaleX to the current scroll progress, smoothed by a spring. Because the theme
gives it origin-left, the scale grows from the left edge to the right, so the
bar reads as a fill.
The visible bar is entirely up to you: the theme only supplies the geometry
(inset-x-0 top-0 h-1 origin-left), so add a background colour and a position
(your own fixed/sticky, or scoped to a container) through class.
Usage
Give the bar a colour and a position with class. Here it is pinned to the top
of the window above a long article.
Scroll Progress Indicator
Track your reading progress with this visual indicator that updates as you scroll
Introduction
The scroll progress indicator is a subtle yet effective way to show users how much of a page they've read and how much remains. This can be particularly useful for long articles, documentation, or any content where users need to track their progress.
Benefits
- Improves user experience by providing visual feedback
- Helps users track their progress through long content
- Encourages completion of articles or forms
- Subtle animation that doesn't distract from content
- Works seamlessly across all device sizes
Implementation
The scroll progress indicator is easy to implement and customize:
<MScrollProgress class="bg-primary fixed top-0 z-50" />
Advanced Features
Customize the appearance and behavior to match your design system:
Colors
Match your brand colors
Height
Adjust thickness
Position
Top or bottom
Conclusion
Adding a scroll progress indicator to your website or application is a simple way to enhance the user experience. It provides valuable feedback and helps users navigate through your content more effectively. Try implementing it today to see the difference!
<template>
<MScrollProgress class="bg-primary fixed top-0 z-50" />
<div class="space-y-8 p-8 max-w-4xl mx-auto">
<div class="text-center mb-12">
<h2 class="text-4xl font-bold text-highlighted mb-4">
Scroll Progress Indicator
</h2>
<p class="text-muted text-lg">
Track your reading progress with this visual indicator that updates as you scroll
</p>
</div>
<UCard>
<template #header>
<h3 class="text-2xl font-bold text-highlighted">
Introduction
</h3>
</template>
<p class="text-muted text-lg leading-relaxed">
The scroll progress indicator is a subtle yet effective way to show users how much of a page
they've read and how much remains. This can be particularly useful for long articles,
documentation, or any content where users need to track their progress.
</p>
</UCard>
<UCard>
<template #header>
<h3 class="text-2xl font-bold text-highlighted">
Benefits
</h3>
</template>
<ul class="list-disc pl-6 space-y-3 text-muted text-lg">
<li class="pl-2">
Improves user experience by providing visual feedback
</li>
<li class="pl-2">
Helps users track their progress through long content
</li>
<li class="pl-2">
Encourages completion of articles or forms
</li>
<li class="pl-2">
Subtle animation that doesn't distract from content
</li>
<li class="pl-2">
Works seamlessly across all device sizes
</li>
</ul>
</UCard>
<UCard>
<template #header>
<h3 class="text-2xl font-bold text-highlighted">
Implementation
</h3>
</template>
<p class="text-muted mb-6 text-lg">
The scroll progress indicator is easy to implement and customize:
</p>
<div class="bg-muted p-5 rounded-lg">
<pre class="text-sm text-highlighted overflow-x-auto"><MScrollProgress class="bg-primary fixed top-0 z-50" /></pre>
</div>
</UCard>
<UCard>
<template #header>
<h3 class="text-2xl font-bold text-highlighted">
Advanced Features
</h3>
</template>
<div class="space-y-4">
<p class="text-muted text-lg">
Customize the appearance and behavior to match your design system:
</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="p-4 bg-primary/5 dark:bg-primary/30 rounded-lg">
<h4 class="font-bold text-primary mb-2">
Colors
</h4>
<p class="text-sm text-primary">
Match your brand colors
</p>
</div>
<div class="p-4 bg-purple-50 dark:bg-purple-900/30 rounded-lg">
<h4 class="font-bold text-purple-700 dark:text-purple-300 mb-2">
Height
</h4>
<p class="text-sm text-purple-600 dark:text-purple-400">
Adjust thickness
</p>
</div>
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
<h4 class="font-bold text-green-700 dark:text-green-300 mb-2">
Position
</h4>
<p class="text-sm text-green-600 dark:text-green-400">
Top or bottom
</p>
</div>
</div>
</div>
</UCard>
<UCard>
<template #header>
<h3 class="text-2xl font-bold text-highlighted">
Conclusion
</h3>
</template>
<p class="text-muted text-lg leading-relaxed">
Adding a scroll progress indicator to your website or application is a simple way to
enhance the user experience. It provides valuable feedback and helps users navigate
through your content more effectively. Try implementing it today to see the difference!
</p>
</UCard>
<div class="text-center py-8">
<UButton
color="primary"
variant="solid"
size="lg"
>
<UIcon
name="i-lucide-download"
class="w-5 h-5 mr-2"
/>
Download Component
</UButton>
</div>
</div>
</template>
Examples
Scoped to a scroll container
Pass containerRef as a getter — :container-ref="() => panelRef" — to track
a specific scrollable element instead of the window. A plain binding is unwrapped
by the template and captured as null during setup, after which nothing re-reads
it and useScroll falls back to the window.
Panel content line 1
Panel content line 2
Panel content line 3
Panel content line 4
Panel content line 5
Panel content line 6
Panel content line 7
Panel content line 8
Panel content line 9
Panel content line 10
Panel content line 11
Panel content line 12
The bar tracks the panel's own scroll, not the page.
<template>
<div class="w-full max-w-lg space-y-3">
<div
ref="panelRef"
class="relative h-64 overflow-y-auto rounded-2xl border border-default"
>
<MScrollProgress
:container-ref="() => panelRef"
class="sticky top-0 h-1 w-full bg-primary"
/>
<div class="space-y-4 p-6">
<p
v-for="i in 12"
:key="i"
class="text-muted"
>
Panel content line {{ i }}
</p>
</div>
</div>
<p class="text-sm text-muted">
The bar tracks the panel's own scroll, not the page.
</p>
</div>
</template>
<script setup lang="ts">
const panelRef = ref<HTMLElement | null>(null)
</script>
Custom spring options
springOptions tunes how quickly the bar catches up to the scroll position. A
lower stiffness and damping make the bar trail the scroll with a soft lag.
<template>
<MScrollProgress
:spring-options="{ stiffness: 100, damping: 30 }"
class="bg-gradient-to-r from-emerald-500 to-teal-600"
/>
<div class="space-y-4 p-4">
<div
v-for="i in 10"
:key="i"
class="h-32 bg-primary rounded"
/>
</div>
</template>
Customize
The bar takes no scalar props beyond its spring, so the playground below seeds a
visible class and shows the default springOptions.
Customize
Props
| Prop | Type | Default | Description |
|---|---|---|---|
springOptions | SpringOptions | { stiffness: 200, damping: 50, restDelta: 0.001 } | Motion spring options for the progress value. Merged over the defaults, so
supply only the keys you want to change. A lower stiffness and damping make
the bar lag the scroll more. |
containerRef | MaybeComputedElementRef | - | Scrollable ancestor to track instead of the window. Pass a getter
(:container-ref="() => panelRef") — see the example above. Left undefined, the
progress follows document scroll. |
class | string | - | Merged onto the bar. The theme only supplies geometry and origin-left, so add
a background colour and a position (or sticky/fixed) here. |
UI
This component's theme exposes the slots below. Override any of them per instance with the ui prop, or app-wide under ui.scrollProgress in app.config.ts.
| Prop | Type | Default | Description |
|---|---|---|---|
ui | object | - | Per-slot classes merged over the theme defaults. |
| Slot | Props | Description |
|---|---|---|
root | - | inset-x-0 top-0 h-1 origin-left |
Accessibility
The bar is deliberately non-semantic: it is a bare motion element with no role,
no ARIA and no aria-valuenow. It is a purely visual affordance and adds nothing
to the accessibility tree, so screen-reader users are neither helped nor
hindered by it.
That also means it carries no information a screen reader can announce. If the progress value matters to your content — a multi-step form, for instance — expose it separately with a real progress element or live text rather than relying on this bar.
The component does not read prefers-reduced-motion. The spring is what smooths
the bar; with reduced motion requested you may prefer to pass stiffer
springOptions (a large stiffness) so the bar tracks the scroll without a
noticeable lag.
Performance
- One
useScrollon the window (or your container) and oneuseSpringon the resulting value. Nothing else subscribes to scroll. - The bar animates only
scaleX, a compositor-friendly transform, so each frame repaints nothing and never triggers layout. - Progress is written to a
scaleXstyle, not towidth, which is what keeps the animation off the layout path. - The spring is the same smoothing Motion uses everywhere, so overscroll and rubber-banding settle rather than snapping.
How it works
The theme supplies origin-left on the bar, so a scaleX of 0 collapses it to
the left edge and 1 stretches it across the element. Setting scaleX directly
on a composed layer means the browser can interpolate the transform without
re-laying out the bar's box, which is why a h-1 bar can be driven every frame
for free.
If you omit class entirely the bar still works, but it renders transparent and
in normal flow — the theme intentionally leaves styling to you.