Skip to component
GGamma

Scroll progress

A focusable reading region with a native progress indicator driven by its real scroll position.

New
Design review notes
FIELD NOTES / 04

Start with the task

Understand what a person needs to accomplish before choosing the control. A familiar action with a clear result is easier to evaluate than an ornamental interaction.

Keep the context visible

Use labels and nearby instructions to explain the meaning of each value. Keep the important context available while the person changes a setting or reviews a result.

Review every state

Try an empty collection, a disabled action and a failed request. Each state should explain the next useful step. A narrow layout should preserve those same actions and their labels.

Respect the reader

Preserve visible focus and native scrolling. Let pointer, touch and keyboard input use the same reading region. Decorative effects should leave the content and essential actions available with reduced motion.

Keep the reference useful

Record why a pattern fits, which states you tested and where its guidance came from. A working example and a clear source link make the next design decision easier.

Focus the reading region and scroll to see its measured progress.

Live previewUse keyboard to interact

Implementation

1"use client";2 3import { useEffect, useRef, useState, type HTMLAttributes } from "react";4 5export type ScrollProgressProps = HTMLAttributes<HTMLDivElement> & {6  /** Visible title and accessible name of the native scroll region. */7  label: string;8  /** Viewport height in pixels. */9  height?: number;10  /** Measured scroll position from zero to one, after viewport/content changes. */11  onProgressChange?: (progress: number) => void;12};13 14/** Reading position measured from the component's actual native scroll region. */15export function ScrollProgress({16  label,17  children,18  height = 240,19  onProgressChange,20  onScroll,21  className = "",22  ...divProps23}: ScrollProgressProps) {24  const viewportRef = useRef<HTMLDivElement>(null);25  const contentRef = useRef<HTMLDivElement>(null);26  const lastProgress = useRef(-1);27  const [progress, setProgress] = useState(0);28  const viewportHeight = Number.isFinite(height)