Skip to main content
Photography of Alvaro Montoro being a doofus
Alvaro Montoro

Jill's Husband

CSS-Only Reading Progress Indicator

css html webdev

You may have noticed those reading progress indicators on top of some pages (mainly news articles and blog posts). They are a bar that grows as you read the article and lets you know how much you have left to read visually. This component is a nice feature, especially on mobile, where the scrollbars are not always visible. But did you know that you can code a reading indicator with just a single HTML element and some CSS? No JavaScript code at all.

In this post, I will explain how to build a reading indicator in CSS. You can check the source code on this CodePen demo:

The Code

First, you will need only one empty HTML element:

<div class="reading-indicator"></div>

This tag goes at the page's root (using absolute positioning to get the document's height), and then we will use the ::before and ::after pseudo-elements to "draw" the progress bar.

Note: We could create a more straightforward reading progress indicator without an explicit element, directly in the <body> with its ::before and ::after... but, call me old-fashioned, I prefer not to have "unexpected side-effects," so I like to create an element for it.

The style for the reading-indicator class will be as follows:

.reading-progress {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: calc(100% - 100vh);
   z-index: -1;
   overflow: hidden;
}

A couple of important things related to this code:

  • The height is a funky value; why is that? Why not just 100%? Using 100% would take the page's full size, making the reading indicator stop progressing before reaching 100% (because the 100% would be the exact bottom of the page). We need to consider the height of the view frame (100vh) and refactor it into the element's height.
  • overflow: hidden would not be required, but as you may have noticed, the indicator edge is diagonal instead of vertical, so after reaching 100%, the whole bar wouldn't take the full width. To fix that, I make the pseudo-elements a bit wider than their container and use the overflow: hidden to avoid unnecessary horizontal scroll bars.

This element is only the container. What draws the reading indicator is the pseudo-elements: with ::before, we will draw a horizontal bar that occupies the whole width and has a height and sticks to the top of the page:

.reading-progress::before {
   content: "";
   position: fixed;
   top: 0;
   left: -0.5%;
   width: 101%;
   height: 10px;
   background: linear-gradient(90deg, #369, #396);
}

But with this, we just have a horizontal line. So how do we make it grow and shrink as the user scrolls around? That's where the trickery begins. We use the ::after pseudo-element that occupies the whole size of the parent and a diagonal background, one half the color of the background (notice this as it will be a problem later) and the other half transparent:

.reading-progress::after {
   content: "";
   position: absolute;
   top: 0;
   left: -0.5%;
   width: 101%;
   height: 100%;
   background: linear-gradient(to bottom left, #fff 50%, #fff0 50%);
}

And one last (but crucial) style change: we need to make the html tag have a relative position, so its height impacts the reading indicator.

html {
   position: relative;
}

That's it. That's the trick: the ::after pseudo-element covers completely the ::before, but as we scroll on the page and progress on the triangular background, we reveal more and more of the bar (remember it has a fixed position so that one is always visible).

Some Thoughts

This way of creating a reading indicator is not perfect; it has some interesting points and also some drawbacks. Here is a short list of pros and cons:

Pros:

  • Simple to code and develop.
  • Responsive (relative units FTW!)
  • Supported by all browsers.

Cons:

  • It requires a flat background.
  • Not visually appealing (the end is tilted)
  • It only works at the page level

With some tweaking, it may work at the article level. But with the current implementation, it is more of a glorified scrollbar indicator than any other thing. Which doesn't mean it is not practical. As mentioned above, it could be engaging in some scenarios, especially on cell phones or tablets.

I did some unsuccessful tests, but I still think there has to be a different way to do it (maybe using sticky and background-attachment?)

Article originally published on