Caprice LogoCaprice UI

Reading Indicator

The scroll-driven reading indicator utility for Tailwind CSS

Installation

npx shadcn@latest add @caprice/reading-indicator

Usage

Import the utility into your global.css file:

global.css
@import "tailwindcss";
@import "@caprice-ui/tailwind/utility/reading-indicator";

The utility exposes two positioning modes — pick whichever fits your layout. Height, color and any other styling come from regular Tailwind utilities.

Sticky (default)

reading-indicator (alias for reading-indicator-sticky) pins the bar via position: sticky. It works in any scroll context — the page itself or any contained scrollable area — without needing an ancestor to establish a containing block.

article.tsx
<article>
  <div class="reading-indicator h-1 bg-primary" />
  <h1>The quiet hum of machines</h1>
  {/* … */}
</article>

Fixed

reading-indicator-fixed pins the bar via position: fixed, so it overlays the viewport edge-to-edge regardless of where it sits in the DOM. Use this when you want the indicator to float above the page content.

article.tsx
<article>
  <div class="reading-indicator-fixed h-1 bg-primary" />
  <h1>The quiet hum of machines</h1>
  {/* … */}
</article>

Note: position: fixed needs the nearest ancestor establishing a containing block to be the viewport (the default) — if a parent applies transform, filter, contain: paint, etc., the indicator will be contained by that ancestor instead. Reach for reading-indicator-sticky if that's getting in your way.

How it works

The utility relies on CSS scroll-driven animations — specifically animation-timeline: scroll(), which binds an animation's progress to the scroll position of the nearest scroll container.

Scroll-driven animations are supported in Chromium 115+ and Safari 26+; Firefox still ships them behind a flag. The utility degrades gracefully — the bar's resting state is scaleX(0), so unsupported browsers render nothing rather than a stuck full-width bar. If you need a visible fallback, gate one behind the @supports at-rule, e.g. @supports not (animation-timeline: scroll()).

Last updated on