Skip to content

How to create a horizontal progress indicator for your Starlight site

A beautiful cover image with the text "Progress Bar"

In this post, we would like to show you how FrostyBee created a clean horizontal progress indicator for Starlight sites.

You will need to have an existing Starlight website.

Let’s create a new component called ProgressIndicator.astro in the src/components/ directory. This component will be responsible for rendering the progress indicator.

First, let’s take a look at a classic solution using JavaScript.

We create a container which spans across the whole width with the progress bar <div> inside. The width of the inner <div> is the updated whenever the user scrolls (and when the page loads) to reflect the current scroll position.

src/components/ProgressIndicator.astro
<div class="progress-scroll-container" aria-hidden="true">
<div id="progress-scroll"></div>
</div>
<script>
window.addEventListener("scroll", function () {
updateProgressScroll();
});
window.addEventListener("load", function () {
updateProgressScroll();
});
function updateProgressScroll() {
if (document) {
const progressScroll = document.getElementById("progress-scroll");
if (progressScroll) {
var scrollTop = window.scrollY || document.documentElement.scrollTop;
var scrollHeight =
document.documentElement.scrollHeight - window.innerHeight;
var progress = (scrollTop / scrollHeight) * 100;
progressScroll.style.width = progress + "%";
}
}
}
</script>
17 collapsed lines
<style>
html:is(:not([data-has-hero]), [data-has-sidebar], [data-has-toc]) .progress-scroll-container {
position: fixed;
top: var(--sl-nav-height);
left: 0;
height: 0.25rem;
width: 100%;
background-color: transparent;
z-index: 3;
}
#progress-scroll {
height: 100%;
width: 0px;
background-color: var(--sl-color-accent-high);
}
</style>
<style is:global>
#starlight__on-this-page--mobile {
margin-top: 0.25rem;
}
</style>

In the hidden lines is the styling which makes sure that the progress bar looks like a typical progress bar. It is also suited to Starlight because the bar is automatically hidden when the site has no sidebar and table of contents, but a hero element as most landing pages do.

One thing to point out is what the global CSS (line 45) does: It pushes the mobile table of contents dropdown menu down to create more whitespace to the progress bar. This is a personal preference and you can remove it if you want.

Now we can use this component in a Starlight override. In this case it makes sense to override the Header.astro component like this:

src/components/Header.astro
---
import Default from "@astrojs/starlight/components/Header.astro";
import ScrollIndicator from "./ScrollIndicator.astro";
---
<Default><slot /></Default>
<ScrollIndicator />

Do not forget to add this component override to your astro.config.mjs file:

astro.config.mjs
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
export default defineConfig({
integrations: [
starlight({
title: 'My Docs with a Progress Indicator',
components: {
// Override the default `Header` component.
Header: './src/components/Header.astro',
},
}),
],
});

You can find the whole project code on GitHub and in this Stackblitz example.

With this setup, the progress indicator will be displayed on every page (except landing pages) of your Starlight site. 🥳