CSS-Only Reading Position Indicator
Tags: css, html, webdev
Published: 2022-10-03 01:17:40

---
title: CSS-Only Reading Position Indicator
published: true
description: How to create a reading indicator with CSS only
tags: css,html,webdev
canonical_url: https://alvaromontoro.com/blog/68014/css-only-reading-progress-indicator
---

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](https://codepen.io/alvaromontoro/pen/RwywmmR):

{% codepen https://codepen.io/alvaromontoro/pen/RwywmmR %}


## The Code

First, you will need only one empty HTML element:

```html
<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:

```scss
.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:

```scss
.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:

```scss
.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.

```scss
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?)