Scroll Progress β
This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.
The Scroll Progress component provides visual feedback about how far the user has scrolled through a document or scrollable container. It offers four distinct visual modes to fit different design contexts.
Examples β
Live Preview
Active Scroll Indicators
Toggle modes on/off to see different scroll progress indicators in action. Scroll down to see them update!
Bar Mode
The classic scroll progress bar. Positioned at the top (or bottom) of the viewport, it provides a clear, unobtrusive indication of scroll depth. Perfect for long-form content like articles and documentation.
Props
orientation: 'top' or 'bottom'size: Uses small size for subtle appearance
Dot Trail Mode
A minimalist approach using a trail of dots. As you scroll, dots fill from left to right. Great for presentations, multi-step forms, or when you want a less prominent indicator.
Props
dots: Number of dots to display (default: 5)badgeVariant: Color variant for filled dots
Badge Mode
Displays the exact scroll percentage in a badge. Useful when users need precise feedback about their position in the content. Can be positioned anywhere on the page.
Props
badgeVariant: Badge color variantprecision: Decimal places (0 for integers like "47%")
Ring Mode
A circular progress ring that fills as you scroll. Often paired with floating action buttons or scroll-to-top functionality. Provides an elegant, space-efficient indicator.
Props
ringSize: Diameter in pixels (default: 32)ringStrokeWidth: Thickness of the ring (default: 3)ringVariant: Color variant
Bar Size Variants
Progress bars support three sizes via the underlying Progress component.
Small (8px) - Default for scroll progress
Medium (12px)
Precision Control
Control decimal places in percentage display with the precision prop.
precision={0}
Integers (47%)
precision={1}
One decimal (47.3%)
precision={2}
Two decimals (47.39%)
Color Variants
Both badge and ring modes support color variants.
Badge Variants
Ring Variants
Primary
Success
Warning
Danger
Info
Keep Scrolling!
Scroll down to see the active progress indicators update in real-time.
Paragraph 1: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 2: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 3: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 4: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 5: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 6: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 7: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 8: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 9: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 10: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 11: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 12: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 13: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 14: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 15: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 16: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 17: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 18: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 19: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 20: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 21: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 22: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 23: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 24: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 25: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 26: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 27: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 28: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 29: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Paragraph 30: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
π You've reached the end!
Notice how all active progress indicators show 100%
View Vue Code
<template>
<section>
<!-- Mode Controls -->
<div
class="mbe4"
style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md); position: sticky; top: 0; z-index: 1000;"
>
<h2 style="margin-top: 0;">Active Scroll Indicators</h2>
<p
class="mbs2 mbe3"
style="font-size: 0.875rem; color: var(--ag-text-secondary);"
>
Toggle modes on/off to see different scroll progress indicators in action. Scroll down to see them update!
</p>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<VueToggle v-model:checked="showBar" />
<span>Bar (Top)</span>
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<VueToggle v-model:checked="showDotTrail" />
<span>Dot Trail</span>
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<VueToggle v-model:checked="showBadge" />
<span>Badge (Bottom Left)</span>
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<VueToggle v-model:checked="showRing" />
<span>Ring (Bottom Right)</span>
</label>
</div>
</div>
<!-- Active Scroll Progress Indicators -->
<VueScrollProgress
v-if="showBar"
mode="bar"
orientation="top"
/>
<div
v-if="showDotTrail"
style="position: fixed; top: 5rem; right: 3%; z-index: 100;"
>
<VueScrollProgress
mode="dot-trail"
:dots="7"
badge-variant="success"
/>
</div>
<div
v-if="showBadge"
style="position: fixed; bottom: var(--ag-space-4); left: var(--ag-space-4); z-index: 100;"
>
<VueScrollProgress
mode="badge"
badge-variant="info"
/>
</div>
<div
v-if="showRing"
style="position: fixed; bottom: var(--ag-space-4); right: var(--ag-space-4); z-index: 100;"
>
<VueScrollProgress
mode="ring"
:ring-size="48"
ring-variant="primary"
/>
</div>
<!-- Individual Mode Explanations -->
<div class="mbe4">
<h2>Bar Mode</h2>
<p class="mbs2 mbe3">
The classic scroll progress bar. Positioned at the top (or bottom) of the viewport, it provides a clear,
unobtrusive indication of scroll depth. Perfect for long-form content like articles and documentation.
</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
<h3 style="margin-top: 0; font-size: 0.875rem;">Props</h3>
<ul style="margin: 0; padding-left: 1.5rem; font-size: 0.875rem;">
<li><code>orientation</code>: 'top' or 'bottom'</li>
<li><code>size</code>: Uses small size for subtle appearance</li>
</ul>
</div>
</div>
<div class="mbe4">
<h2>Dot Trail Mode</h2>
<p class="mbs2 mbe3">
A minimalist approach using a trail of dots. As you scroll, dots fill from left to right.
Great for presentations, multi-step forms, or when you want a less prominent indicator.
</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
<h3 style="margin-top: 0; font-size: 0.875rem;">Props</h3>
<ul style="margin: 0; padding-left: 1.5rem; font-size: 0.875rem;">
<li><code>dots</code>: Number of dots to display (default: 5)</li>
<li><code>badgeVariant</code>: Color variant for filled dots</li>
</ul>
</div>
</div>
<div class="mbe4">
<h2>Badge Mode</h2>
<p class="mbs2 mbe3">
Displays the exact scroll percentage in a badge. Useful when users need precise feedback
about their position in the content. Can be positioned anywhere on the page.
</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
<h3 style="margin-top: 0; font-size: 0.875rem;">Props</h3>
<ul style="margin: 0; padding-left: 1.5rem; font-size: 0.875rem;">
<li><code>badgeVariant</code>: Badge color variant</li>
<li><code>precision</code>: Decimal places (0 for integers like "47%")</li>
</ul>
</div>
</div>
<div class="mbe4">
<h2>Ring Mode</h2>
<p class="mbs2 mbe3">
A circular progress ring that fills as you scroll. Often paired with floating action buttons
or scroll-to-top functionality. Provides an elegant, space-efficient indicator.
</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
<h3 style="margin-top: 0; font-size: 0.875rem;">Props</h3>
<ul style="margin: 0; padding-left: 1.5rem; font-size: 0.875rem;">
<li><code>ringSize</code>: Diameter in pixels (default: 32)</li>
<li><code>ringStrokeWidth</code>: Thickness of the ring (default: 3)</li>
<li><code>ringVariant</code>: Color variant</li>
</ul>
</div>
</div>
<!-- Size Variants (Bar Mode) -->
<div class="mbe4">
<h2>Bar Size Variants</h2>
<p class="mbs2 mbe3">Progress bars support three sizes via the underlying Progress component.</p>
<div style="display: flex; flex-direction: column; gap: 2rem;">
<div>
<p style="margin: 0 0 0.5rem 0; font-size: 0.875rem; font-weight: 500;">Small (8px) - Default for scroll progress</p>
<VueScrollProgress
mode="bar"
orientation="top"
style="position: static;"
/>
</div>
<div>
<p style="margin: 0 0 0.5rem 0; font-size: 0.875rem; font-weight: 500;">Medium (12px)</p>
<div style="position: relative;">
<ag-scroll-progress
mode="bar"
orientation="top"
style="position: static;"
></ag-scroll-progress>
</div>
</div>
</div>
</div>
<!-- Precision Control -->
<div class="mbe4">
<h2>Precision Control</h2>
<p class="mbs2 mbe3">Control decimal places in percentage display with the <code>precision</code> prop.</p>
<div style="display: flex; gap: 2rem; flex-wrap: wrap;">
<div style="text-align: center;">
<VueScrollProgress
mode="badge"
:precision="0"
badge-variant="primary"
style="position: static;"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">precision={0}</p>
<p style="margin: 0; font-size: 0.75rem; color: var(--ag-text-secondary);">Integers (47%)</p>
</div>
<div style="text-align: center;">
<VueScrollProgress
mode="badge"
:precision="1"
badge-variant="success"
style="position: static;"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">precision={1}</p>
<p style="margin: 0; font-size: 0.75rem; color: var(--ag-text-secondary);">One decimal (47.3%)</p>
</div>
<div style="text-align: center;">
<VueScrollProgress
mode="badge"
:precision="2"
badge-variant="warning"
style="position: static;"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">precision={2}</p>
<p style="margin: 0; font-size: 0.75rem; color: var(--ag-text-secondary);">Two decimals (47.39%)</p>
</div>
</div>
</div>
<!-- Variants -->
<div class="mbe4">
<h2>Color Variants</h2>
<p class="mbs2 mbe3">Both badge and ring modes support color variants.</p>
<h3 style="font-size: 1rem; margin-bottom: 1rem;">Badge Variants</h3>
<div style="display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 2rem;">
<VueScrollProgress
mode="badge"
badge-variant="default"
style="position: static;"
/>
<VueScrollProgress
mode="badge"
badge-variant="success"
style="position: static;"
/>
<VueScrollProgress
mode="badge"
badge-variant="warning"
style="position: static;"
/>
<VueScrollProgress
mode="badge"
badge-variant="danger"
style="position: static;"
/>
<VueScrollProgress
mode="badge"
badge-variant="info"
style="position: static;"
/>
</div>
<h3 style="font-size: 1rem; margin-bottom: 1rem;">Ring Variants</h3>
<div style="display: flex; gap: 2rem; flex-wrap: wrap;">
<div style="text-align: center;">
<VueScrollProgress
mode="ring"
ring-variant="primary"
:ring-size="40"
style="position: static;"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Primary</p>
</div>
<div style="text-align: center;">
<VueScrollProgress
mode="ring"
ring-variant="success"
:ring-size="40"
style="position: static;"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Success</p>
</div>
<div style="text-align: center;">
<VueScrollProgress
mode="ring"
ring-variant="warning"
:ring-size="40"
style="position: static;"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Warning</p>
</div>
<div style="text-align: center;">
<VueScrollProgress
mode="ring"
ring-variant="danger"
:ring-size="40"
style="position: static;"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Danger</p>
</div>
<div style="text-align: center;">
<VueScrollProgress
mode="ring"
ring-variant="info"
:ring-size="40"
style="position: static;"
/>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Info</p>
</div>
</div>
</div>
<!-- Scrollable content to demonstrate -->
<div class="mbe4">
<h2>Keep Scrolling!</h2>
<p class="mbs2 mbe3">Scroll down to see the active progress indicators update in real-time.</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
<p
v-for="i in 30"
:key="i"
style="margin: 0.5rem 0; color: var(--ag-text-secondary);"
>
Paragraph {{ i }}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
</div>
</div>
<div
class="mbe4"
style="text-align: center; padding: var(--ag-space-8) 0;"
>
<h2>π You've reached the end!</h2>
<p style="color: var(--ag-text-secondary);">Notice how all active progress indicators show 100%</p>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { VueScrollProgress } from "agnosticui-core/scroll-progress/vue";
import { VueToggle } from "agnosticui-core/toggle/vue";
export default defineComponent({
name: "ScrollProgressExamples",
components: {
VueScrollProgress,
VueToggle,
},
setup() {
// Start with all modes enabled so users discover all features
const showBar = ref(true);
const showDotTrail = ref(true);
const showBadge = ref(true);
const showRing = ref(true);
return {
showBar,
showDotTrail,
showBadge,
showRing,
};
},
});
</script>
<style scoped>
/* Ensure toggle labels are readable */
label {
user-select: none;
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/scroll-progress';
import 'agnosticui-core/toggle';
export class ScrollProgressLitExamples extends LitElement {
static properties = {
showBar: { type: Boolean },
showDotTrail: { type: Boolean },
showBadge: { type: Boolean },
showRing: { type: Boolean },
};
constructor() {
super();
this.showBar = true;
this.showDotTrail = true;
this.showBadge = true;
this.showRing = true;
}
createRenderRoot() {
return this;
}
handleBarToggle(e) {
this.showBar = e.target.checked;
}
handleDotTrailToggle(e) {
this.showDotTrail = e.target.checked;
}
handleBadgeToggle(e) {
this.showBadge = e.target.checked;
}
handleRingToggle(e) {
this.showRing = e.target.checked;
}
render() {
return html`
<section>
<!-- Mode Controls -->
<div
class="mbe4"
style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md); position: sticky; top: 0; z-index: 1000;"
>
<h2 style="margin-top: 0;">Active Scroll Indicators</h2>
<p
class="mbs2 mbe3"
style="font-size: 0.875rem; color: var(--ag-text-secondary);"
>
Toggle modes on/off to see different scroll progress indicators in action. Scroll down to see them update!
</p>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<ag-toggle ?checked="${this.showBar}" @click="${(e) => { this.showBar = !this.showBar}}"></ag-toggle>
<span>Bar (Top)</span>
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<ag-toggle ?checked="${this.showDotTrail}" @click="${(e) => { this.showDotTrail = !this.showDotTrail}}"></ag-toggle>
<span>Dot Trail</span>
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<ag-toggle ?checked="${this.showBadge}" @click="${(e) => { this.showBadge = !this.showBadge}}"></ag-toggle>
<span>Badge (Bottom Left)</span>
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<ag-toggle ?checked="${this.showRing}" @click="${(e) => { this.showRing = !this.showRing}}"></ag-toggle>
<span>Ring (Bottom Right)</span>
</label>
</div>
</div>
<!-- Active Scroll Progress Indicators -->
${this.showBar
? html`<ag-scroll-progress mode="bar" orientation="top"></ag-scroll-progress>`
: ''}
${this.showDotTrail
? html`
<div style="position: fixed; top: 5rem; right: 3%; z-index: 100;">
<ag-scroll-progress
mode="dot-trail"
dots="7"
badge-variant="success"
></ag-scroll-progress>
</div>
`
: ''}
${this.showBadge
? html`
<div style="position: fixed; bottom: var(--ag-space-4); left: var(--ag-space-4); z-index: 100;">
<ag-scroll-progress
mode="badge"
badge-variant="info"
></ag-scroll-progress>
</div>
`
: ''}
${this.showRing
? html`
<div style="position: fixed; bottom: var(--ag-space-4); right: var(--ag-space-4); z-index: 100;">
<ag-scroll-progress
mode="ring"
ring-size="48"
ring-variant="primary"
></ag-scroll-progress>
</div>
`
: ''}
<!-- Individual Mode Explanations -->
<div class="mbe4">
<h2>Bar Mode</h2>
<p class="mbs2 mbe3">
The classic scroll progress bar. Positioned at the top (or bottom) of the viewport, it provides a clear,
unobtrusive indication of scroll depth. Perfect for long-form content like articles and documentation.
</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
<h3 style="margin-top: 0; font-size: 0.875rem;">Props</h3>
<ul style="margin: 0; padding-left: 1.5rem; font-size: 0.875rem;">
<li><code>orientation</code>: 'top' or 'bottom'</li>
<li><code>size</code>: Uses small size for subtle appearance</li>
</ul>
</div>
</div>
<div class="mbe4">
<h2>Dot Trail Mode</h2>
<p class="mbs2 mbe3">
A minimalist approach using a trail of dots. As you scroll, dots fill from left to right.
Great for presentations, multi-step forms, or when you want a less prominent indicator.
</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
<h3 style="margin-top: 0; font-size: 0.875rem;">Props</h3>
<ul style="margin: 0; padding-left: 1.5rem; font-size: 0.875rem;">
<li><code>dots</code>: Number of dots to display (default: 5)</li>
<li><code>badge-variant</code>: Color variant for filled dots</li>
</ul>
</div>
</div>
<div class="mbe4">
<h2>Badge Mode</h2>
<p class="mbs2 mbe3">
Displays the exact scroll percentage in a badge. Useful when users need precise feedback
about their position in the content. Can be positioned anywhere on the page.
</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
<h3 style="margin-top: 0; font-size: 0.875rem;">Props</h3>
<ul style="margin: 0; padding-left: 1.5rem; font-size: 0.875rem;">
<li><code>badge-variant</code>: Badge color variant</li>
<li><code>precision</code>: Decimal places (0 for integers like "47%")</li>
</ul>
</div>
</div>
<div class="mbe4">
<h2>Ring Mode</h2>
<p class="mbs2 mbe3">
A circular progress ring that fills as you scroll. Often paired with floating action buttons
or scroll-to-top functionality. Provides an elegant, space-efficient indicator.
</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
<h3 style="margin-top: 0; font-size: 0.875rem;">Props</h3>
<ul style="margin: 0; padding-left: 1.5rem; font-size: 0.875rem;">
<li><code>ring-size</code>: Diameter in pixels (default: 32)</li>
<li><code>ring-stroke-width</code>: Thickness of the ring (default: 3)</li>
<li><code>ring-variant</code>: Color variant</li>
</ul>
</div>
</div>
<!-- Size Variants (Bar Mode) -->
<div class="mbe4">
<h2>Bar Size Variants</h2>
<p class="mbs2 mbe3">Progress bars support three sizes via the underlying Progress component.</p>
<div style="display: flex; flex-direction: column; gap: 2rem;">
<div>
<p style="margin: 0 0 0.5rem 0; font-size: 0.875rem; font-weight: 500;">Small (8px) - Default for scroll progress</p>
<ag-scroll-progress
mode="bar"
orientation="top"
style="position: static;"
></ag-scroll-progress>
</div>
<div>
<p style="margin: 0 0 0.5rem 0; font-size: 0.875rem; font-weight: 500;">Medium (12px)</p>
<div style="position: relative;">
<ag-scroll-progress
mode="bar"
orientation="top"
style="position: static;"
></ag-scroll-progress>
</div>
</div>
</div>
</div>
<!-- Precision Control -->
<div class="mbe4">
<h2>Precision Control</h2>
<p class="mbs2 mbe3">Control decimal places in percentage display with the <code>precision</code> prop.</p>
<div style="display: flex; gap: 2rem; flex-wrap: wrap;">
<div style="text-align: center;">
<ag-scroll-progress
mode="badge"
precision="0"
badge-variant="primary"
style="position: static;"
></ag-scroll-progress>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">precision={0}</p>
<p style="margin: 0; font-size: 0.75rem; color: var(--ag-text-secondary);">Integers (47%)</p>
</div>
<div style="text-align: center;">
<ag-scroll-progress
mode="badge"
precision="1"
badge-variant="success"
style="position: static;"
></ag-scroll-progress>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">precision={1}</p>
<p style="margin: 0; font-size: 0.75rem; color: var(--ag-text-secondary);">One decimal (47.3%)</p>
</div>
<div style="text-align: center;">
<ag-scroll-progress
mode="badge"
precision="2"
badge-variant="warning"
style="position: static;"
></ag-scroll-progress>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">precision={2}</p>
<p style="margin: 0; font-size: 0.75rem; color: var(--ag-text-secondary);">Two decimals (47.39%)</p>
</div>
</div>
</div>
<!-- Variants -->
<div class="mbe4">
<h2>Color Variants</h2>
<p class="mbs2 mbe3">Both badge and ring modes support color variants.</p>
<h3 style="font-size: 1rem; margin-bottom: 1rem;">Badge Variants</h3>
<div style="display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 2rem;">
<ag-scroll-progress
mode="badge"
badge-variant="default"
style="position: static;"
></ag-scroll-progress>
<ag-scroll-progress
mode="badge"
badge-variant="success"
style="position: static;"
></ag-scroll-progress>
<ag-scroll-progress
mode="badge"
badge-variant="warning"
style="position: static;"
></ag-scroll-progress>
<ag-scroll-progress
mode="badge"
badge-variant="danger"
style="position: static;"
></ag-scroll-progress>
<ag-scroll-progress
mode="badge"
badge-variant="info"
style="position: static;"
></ag-scroll-progress>
</div>
<h3 style="font-size: 1rem; margin-bottom: 1rem;">Ring Variants</h3>
<div style="display: flex; gap: 2rem; flex-wrap: wrap;">
<div style="text-align: center;">
<ag-scroll-progress
mode="ring"
ring-variant="primary"
ring-size="40"
style="position: static;"
></ag-scroll-progress>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Primary</p>
</div>
<div style="text-align: center;">
<ag-scroll-progress
mode="ring"
ring-variant="success"
ring-size="40"
style="position: static;"
></ag-scroll-progress>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Success</p>
</div>
<div style="text-align: center;">
<ag-scroll-progress
mode="ring"
ring-variant="warning"
ring-size="40"
style="position: static;"
></ag-scroll-progress>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Warning</p>
</div>
<div style="text-align: center;">
<ag-scroll-progress
mode="ring"
ring-variant="danger"
ring-size="40"
style="position: static;"
></ag-scroll-progress>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Danger</p>
</div>
<div style="text-align: center;">
<ag-scroll-progress
mode="ring"
ring-variant="info"
ring-size="40"
style="position: static;"
></ag-scroll-progress>
<p style="margin-top: 0.5rem; font-size: 0.875rem;">Info</p>
</div>
</div>
</div>
<!-- Scrollable content to demonstrate -->
<div class="mbe4">
<h2>Keep Scrolling!</h2>
<p class="mbs2 mbe3">Scroll down to see the active progress indicators update in real-time.</p>
<div style="background: var(--ag-background-secondary); padding: var(--ag-space-4); border-radius: var(--ag-radius-md);">
${Array.from({ length: 30 }).map(
(_, i) => html`
<p style="margin: 0.5rem 0; color: var(--ag-text-secondary);">
Paragraph ${i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
`
)}
</div>
</div>
<div
class="mbe4"
style="text-align: center; padding: var(--ag-space-8) 0;"
>
<h2>π You've reached the end!</h2>
<p style="color: var(--ag-text-secondary);">Notice how all active progress indicators show 100%</p>
</div>
</section>
`;
}
}
customElements.define('scroll-progress-lit-examples', ScrollProgressLitExamples);
Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.
View React Code
import { useState } from "react";
import { ReactScrollProgress } from "agnosticui-core/scroll-progress/react";
import { ReactToggle } from "agnosticui-core/toggle/react";
export default function ScrollProgressReactExamples() {
const [showBar, setShowBar] = useState(true);
const [showDotTrail, setShowDotTrail] = useState(true);
const [showBadge, setShowBadge] = useState(true);
const [showRing, setShowRing] = useState(true);
return (
<section>
{/* Mode Controls */}
<div
className="mbe4"
style={{
background: "var(--ag-background-secondary)",
padding: "var(--ag-space-4)",
borderRadius: "var(--ag-radius-md)",
position: "sticky",
top: 0,
zIndex: 1000,
}}
>
<h2 style={{ marginTop: 0 }}>Active Scroll Indicators</h2>
<p
className="mbs2 mbe3"
style={{ fontSize: "0.875rem", color: "var(--ag-text-secondary)" }}
>
Toggle modes on/off to see different scroll progress indicators in action. Scroll down to see them update!
</p>
<div style={{ display: "flex", gap: "1rem", flexWrap: "wrap" }}>
<label style={{ display: "flex", alignItems: "center", gap: "0.5rem", cursor: "pointer" }}>
<ReactToggle
checked={showBar}
onChange={() => setShowBar(!showBar)}
/>
<span>Bar (Top)</span>
</label>
<label style={{ display: "flex", alignItems: "center", gap: "0.5rem", cursor: "pointer" }}>
<ReactToggle
checked={showDotTrail}
onChange={() => setShowDotTrail(!showDotTrail)}
/>
<span>Dot Trail</span>
</label>
<label style={{ display: "flex", alignItems: "center", gap: "0.5rem", cursor: "pointer" }}>
<ReactToggle
checked={showBadge}
onChange={() => setShowBadge(!showBadge)}
/>
<span>Badge (Bottom Left)</span>
</label>
<label style={{ display: "flex", alignItems: "center", gap: "0.5rem", cursor: "pointer" }}>
<ReactToggle
checked={showRing}
onChange={() => setShowRing(!showRing)}
/>
<span>Ring (Bottom Right)</span>
</label>
</div>
</div>
{/* Active Scroll Progress Indicators */}
{showBar && (
<ReactScrollProgress mode="bar" orientation="top" />
)}
{showDotTrail && (
<div style={{ position: "fixed", top: "5rem", right: "3%", zIndex: 100 }}>
<ReactScrollProgress
mode="dot-trail"
dots={7}
badgeVariant="success"
/>
</div>
)}
{showBadge && (
<div style={{ position: "fixed", bottom: "var(--ag-space-4)", left: "var(--ag-space-4)", zIndex: 100 }}>
<ReactScrollProgress
mode="badge"
badgeVariant="info"
/>
</div>
)}
{showRing && (
<div style={{ position: "fixed", bottom: "var(--ag-space-4)", right: "var(--ag-space-4)", zIndex: 100 }}>
<ReactScrollProgress
mode="ring"
ringSize={48}
ringVariant="primary"
/>
</div>
)}
{/* Individual Mode Explanations */}
<div className="mbe4">
<h2>Bar Mode</h2>
<p className="mbs2 mbe3">
The classic scroll progress bar. Positioned at the top (or bottom) of the viewport, it provides a clear,
unobtrusive indication of scroll depth. Perfect for long-form content like articles and documentation.
</p>
<div style={{ background: "var(--ag-background-secondary)", padding: "var(--ag-space-4)", borderRadius: "var(--ag-radius-md)" }}>
<h3 style={{ marginTop: 0, fontSize: "0.875rem" }}>Props</h3>
<ul style={{ margin: 0, paddingLeft: "1.5rem", fontSize: "0.875rem" }}>
<li><code>orientation</code>: 'top' or 'bottom'</li>
<li><code>size</code>: Uses small size for subtle appearance</li>
</ul>
</div>
</div>
<div className="mbe4">
<h2>Dot Trail Mode</h2>
<p className="mbs2 mbe3">
A minimalist approach using a trail of dots. As you scroll, dots fill from left to right.
Great for presentations, multi-step forms, or when you want a less prominent indicator.
</p>
<div style={{ background: "var(--ag-background-secondary)", padding: "var(--ag-space-4)", borderRadius: "var(--ag-radius-md)" }}>
<h3 style={{ marginTop: 0, fontSize: "0.875rem" }}>Props</h3>
<ul style={{ margin: 0, paddingLeft: "1.5rem", fontSize: "0.875rem" }}>
<li><code>dots</code>: Number of dots to display (default: 5)</li>
<li><code>badgeVariant</code>: Color variant for filled dots</li>
</ul>
</div>
</div>
<div className="mbe4">
<h2>Badge Mode</h2>
<p className="mbs2 mbe3">
Displays the exact scroll percentage in a badge. Useful when users need precise feedback
about their position in the content. Can be positioned anywhere on the page.
</p>
<div style={{ background: "var(--ag-background-secondary)", padding: "var(--ag-space-4)", borderRadius: "var(--ag-radius-md)" }}>
<h3 style={{ marginTop: 0, fontSize: "0.875rem" }}>Props</h3>
<ul style={{ margin: 0, paddingLeft: "1.5rem", fontSize: "0.875rem" }}>
<li><code>badgeVariant</code>: Badge color variant</li>
<li><code>precision</code>: Decimal places (0 for integers like "47%")</li>
</ul>
</div>
</div>
<div className="mbe4">
<h2>Ring Mode</h2>
<p className="mbs2 mbe3">
A circular progress ring that fills as you scroll. Often paired with floating action buttons
or scroll-to-top functionality. Provides an elegant, space-efficient indicator.
</p>
<div style={{ background: "var(--ag-background-secondary)", padding: "var(--ag-space-4)", borderRadius: "var(--ag-radius-md)" }}>
<h3 style={{ marginTop: 0, fontSize: "0.875rem" }}>Props</h3>
<ul style={{ margin: 0, paddingLeft: "1.5rem", fontSize: "0.875rem" }}>
<li><code>ringSize</code>: Diameter in pixels (default: 32)</li>
<li><code>ringStrokeWidth</code>: Thickness of the ring (default: 3)</li>
<li><code>ringVariant</code>: Color variant</li>
</ul>
</div>
</div>
{/* Size Variants (Bar Mode) */}
<div className="mbe4">
<h2>Bar Size Variants</h2>
<p className="mbs2 mbe3">Progress bars support three sizes via the underlying Progress component.</p>
<div style={{ display: "flex", flexDirection: "column", gap: "2rem" }}>
<div>
<p style={{ margin: "0 0 0.5rem 0", fontSize: "0.875rem", fontWeight: 500 }}>Small (8px) - Default for scroll progress</p>
<ReactScrollProgress
mode="bar"
orientation="top"
style={{ position: "static" }}
/>
</div>
<div>
<p style={{ margin: "0 0 0.5rem 0", fontSize: "0.875rem", fontWeight: 500 }}>Medium (12px)</p>
<div style={{ position: "relative" }}>
<ReactScrollProgress
mode="bar"
orientation="top"
style={{ position: "static" }}
/>
</div>
</div>
</div>
</div>
{/* Precision Control */}
<div className="mbe4">
<h2>Precision Control</h2>
<p className="mbs2 mbe3">Control decimal places in percentage display with the <code>precision</code> prop.</p>
<div style={{ display: "flex", gap: "2rem", flexWrap: "wrap" }}>
<div style={{ textAlign: "center" }}>
<ReactScrollProgress
mode="badge"
precision={0}
badgeVariant="primary"
style={{ position: "static" }}
/>
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>precision={'{0}'}</p>
<p style={{ margin: 0, fontSize: "0.75rem", color: "var(--ag-text-secondary)" }}>Integers (47%)</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactScrollProgress
mode="badge"
precision={1}
badgeVariant="success"
style={{ position: "static" }}
/>
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>precision={'{1}'}</p>
<p style={{ margin: 0, fontSize: "0.75rem", color: "var(--ag-text-secondary)" }}>One decimal (47.3%)</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactScrollProgress
mode="badge"
precision={2}
badgeVariant="warning"
style={{ position: "static" }}
/>
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>precision={'{2}'}</p>
<p style={{ margin: 0, fontSize: "0.75rem", color: "var(--ag-text-secondary)" }}>Two decimals (47.39%)</p>
</div>
</div>
</div>
{/* Variants */}
<div className="mbe4">
<h2>Color Variants</h2>
<p className="mbs2 mbe3">Both badge and ring modes support color variants.</p>
<h3 style={{ fontSize: "1rem", marginBottom: "1rem" }}>Badge Variants</h3>
<div style={{ display: "flex", gap: "1rem", flexWrap: "wrap", marginBottom: "2rem" }}>
<ReactScrollProgress
mode="badge"
badgeVariant="default"
style={{ position: "static" }}
/>
<ReactScrollProgress
mode="badge"
badgeVariant="success"
style={{ position: "static" }}
/>
<ReactScrollProgress
mode="badge"
badgeVariant="warning"
style={{ position: "static" }}
/>
<ReactScrollProgress
mode="badge"
badgeVariant="danger"
style={{ position: "static" }}
/>
<ReactScrollProgress
mode="badge"
badgeVariant="info"
style={{ position: "static" }}
/>
</div>
<h3 style={{ fontSize: "1rem", marginBottom: "1rem" }}>Ring Variants</h3>
<div style={{ display: "flex", gap: "2rem", flexWrap: "wrap" }}>
<div style={{ textAlign: "center" }}>
<ReactScrollProgress
mode="ring"
ringVariant="primary"
ringSize={40}
style={{ position: "static" }}
/>
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Primary</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactScrollProgress
mode="ring"
ringVariant="success"
ringSize={40}
style={{ position: "static" }}
/>
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Success</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactScrollProgress
mode="ring"
ringVariant="warning"
ringSize={40}
style={{ position: "static" }}
/>
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Warning</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactScrollProgress
mode="ring"
ringVariant="danger"
ringSize={40}
style={{ position: "static" }}
/>
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Danger</p>
</div>
<div style={{ textAlign: "center" }}>
<ReactScrollProgress
mode="ring"
ringVariant="info"
ringSize={40}
style={{ position: "static" }}
/>
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>Info</p>
</div>
</div>
</div>
{/* Scrollable content to demonstrate */}
<div className="mbe4">
<h2>Keep Scrolling!</h2>
<p className="mbs2 mbe3">Scroll down to see the active progress indicators update in real-time.</p>
<div style={{ background: "var(--ag-background-secondary)", padding: "var(--ag-space-4)", borderRadius: "var(--ag-radius-md)" }}>
{Array.from({ length: 30 }).map((_, i) => (
<p key={i} style={{ margin: "0.5rem 0", color: "var(--ag-text-secondary)" }}>
Paragraph {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
))}
</div>
</div>
<div
className="mbe4"
style={{ textAlign: "center", padding: "var(--ag-space-8) 0" }}
>
<h2>π You've reached the end!</h2>
<p style={{ color: "var(--ag-text-secondary)" }}>Notice how all active progress indicators show 100%</p>
</div>
</section>
);
}
Usage β
TIP
The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add ScrollProgressThe CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.
Props β
| Prop | Type | Default | Description |
|---|---|---|---|
mode | 'bar' | 'dot-trail' | 'badge' | 'ring' | 'bar' | Visual representation mode. |
target | HTMLElement | null | null | Custom scroll container (defaults to document). |
orientation | 'top' | 'bottom' | 'top' | Bar placement (bar mode only). |
dots | number | 5 | Number of dots (dot-trail mode only). |
badgeVariant | badge variants | 'info' | Badge color (badge/dot-trail modes). |
ringSize | number | 32 | Ring diameter in pixels (ring mode only). |
ringStrokeWidth | number | 3 | Ring stroke width (ring mode only). |
ringVariant | ring variants | 'info' | Ring color (ring mode only). |
precision | number | 0 | Decimal places for percentage (0 = integers). |
Modes β
Bar Mode β
A horizontal progress bar fixed to the top or bottom of the viewport. Perfect for long-form content like articles and documentation.
Dot Trail Mode β
A subtle row of dots that fill as you scroll. Great for minimalist designs or when you want a less intrusive indicator.
Badge Mode β
Displays the exact scroll percentage in a badge. Useful when users need precise feedback about their position.
Ring Mode β
A circular progress indicator, often used with floating action buttons or in dashboard contexts.
CSS Shadow Parts β
| Part | Description |
|---|---|
scroll-progress-bar | The progress bar element (bar mode). |
scroll-progress-dots | The dot trail container (dot-trail mode). |
scroll-progress-badge | The badge element (badge mode). |
scroll-progress-ring | The progress ring element (ring mode). |
Accessibility β
- All modes include appropriate
aria-labelattributes for screen readers. - The component automatically updates as the user scrolls.
- Respects user's motion preferences (uses design tokens for animations).
Best Practices β
- Bar mode: Best for top-level page navigation and long-form content.
- Dot trail mode: Ideal for presentations, carousels, or multi-step processes.
- Badge mode: Use when precise percentage feedback is valuable.
- Ring mode: Pairs well with scroll-to-top buttons or floating navigation.
- Keep precision at 0 (integers) for cleaner UX unless exact percentages are needed.
- Use contrasting variants to ensure visibility across different backgrounds.
Custom Scroll Container β
Track scroll progress within a specific container instead of the entire document:
<template>
<div>
<VueScrollProgress mode="bar" :target="scrollContainer" />
<div ref="scrollContainer" style="height: 400px; overflow-y: auto;">
<!-- Your scrollable content -->
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { VueScrollProgress } from "agnosticui-core/scroll-progress/vue";
export default defineComponent({
components: { VueScrollProgress },
setup() {
const scrollContainer = ref<HTMLElement | null>(null);
return { scrollContainer };
},
});
</script>