Vanilla Breeze

reading-progress

Fixed-position scroll progress indicator bar. CSS-driven on modern browsers; passive scroll listener as fallback.

Overview

The <reading-progress> component shows a thin bar fixed to the top or bottom of the viewport that fills as the reader scrolls. It joins the doc-side family alongside <page-stats>, <page-toc>, and <page-info>.

The bar is driven by CSS animation-timeline: scroll(root block) when supported, so modern browsers update the visual without running a scroll listener. JavaScript only takes over when the browser lacks support, or when data-for bounds progress against a specific element.

Attributes

Attribute Type Default Description
data-for string ID of the element whose top/bottom bound the progress range. Without it, tracks document scroll.
data-position string top Which viewport edge anchors the bar: top or bottom.

CSS Custom Properties

Property Default Description
--reading-progress-color var(--color-accent) Fill color of the bar.
--reading-progress-height 3px Thickness of the bar.

Usage

Track document scroll

With no data-for, the bar fills as the user scrolls the page. Modern browsers run this entirely in CSS — no scroll listener:

Bound to a specific article

Point data-for at a content element and the bar fills as that element scrolls through the viewport. This path requires JS:

Progressive Enhancement

The component layers cleanly:

  1. HTML. A child <progress> with aria-label is the semantic anchor — meaningful before any script or CSS runs.
  2. CSS. Modern browsers animate the bar via animation-timeline: scroll(root block). No JavaScript is needed when tracking the document.
  3. JS. When data-for is set or animation-timeline is unsupported, a passive, requestAnimationFrame-throttled scroll listener updates the bar.

The pure-CSS path always tracks document scroll. Use data-for only when the bar must reflect a specific article's reading position.

Accessibility

The inner <progress> element exposes the bar to assistive technology with a clear aria-label. The CSS-driven path keeps the bar visual; JS-driven path additionally updates the value attribute on each frame so screen readers can report the current percentage.

The bar is hidden in print (@media print) and respects prefers-reduced-motion by suppressing animation transitions.