Beautiful & Minimal progress indicator built with Motion One

Learn how to build a minimal and beautiful progress indicator with Motion One. It is a great way to spice up your articles, blog posts and documentation.

Sun Feb 26

Written by: Pelle van der Knaap

A progress indicator indicating the scrolling progress on a page

What are we building?

Today we’re building a beautiful, performant and minimalistic scrolling indicator. The indicator is an amazing method to visually give some insight into the reading progress of a page. It is especially great for pages with a ton of content, like blog posts, documentation or articles.

Why Motion One?

Motion One is the new kid on the block when it comes to animation libraries. It is tiny in file size, utilizes the lightning fast browser animation engine and above all, it is incredibly easy to use. It is a great alternative to libraries like Framer Motion, React Spring and Anime.js.

Read more about it here: A short introduction to Motion One.

Alright, show me the code!

This is the simple SVG that we’re going to use and animate.

<svg viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="30" pathLength="1" class="track"></circle>
    <circle cx="50" cy="50" r="30" pathLength="1" class="progress"></circle>

The first circle is the background circle (the darker track), the second circle displays the actual progress.

The pathLength property (MDN) is used to set the length of the stroke. It allows us to gradually draw the stroke around the circle.

The second property that powers the animation is the stroke-dasharray property (MDN). With the property, we can set the length of dashes & gaps in strokes. To accomplish our animation, we initially set the dash to 0 (nothing) and the gap to 1 (full circle). This way, we can gradually increase the dash length to 1 (full circle) based on the scroll position.

Here is the CSS that we’re going to use to style the SVG. We hide the circles by setting the fill property to none.

svg circle {
    /* We don't want to fill the entire circle with a color */
    fill: none;

.track {
    /* Width of the background stroke */
    stroke-width: 10px;
    /* Color of the background stroke */
    stroke: rgb(24, 32, 47);

/* This is the purple stroke */
.progress {
    /* Color of the stroke */
    stroke: rgb(179, 136, 235);
    /* Thickness of the stroke */
    stroke-width: 5px;
    /* Beginning state of the stroke */
    /* We want it empty so we set the gap part to 1 (full circle) */
    /* And the dash part to 0 (empty) */
    stroke-dasharray: 0, 1;

Here is the JS that powers the animation. We simply import the necessary functions from Motion One and use them to animate the stroke-dasharray property of the progress circle.

When creating the progress indicator locally, you should install the motion package locally (either with a CDN or a package manager like NPM or Yarn). I will leave that part up to you as there are too many options to cover in this article.

import { scroll, animate } from "motion";

// We animate the stroke-dasharray property of the progress circle
// The first part (the actual dash) will be transformed from 0 (nothing) to 1 (full) over the course of the scroll
// We slightly overdraw the stroke to prevent a weird gap where the stroke ends meet
scroll(animate(".foreground", { strokeDasharray: ["0,1", "1.01,1.01"] }));

That’s it! We’re done! Congrats! We have a beautiful, minimal and performant progress indicator. You can view the full example, or even fork it to play around with it, on CodeSandbox.

Full example of the Scroll Indicator (CodeSandbox)