# Scroll Progress

> A reading-progress pill with a ring, the current section, and an expandable menu for jumping through a page.

- Name: `scroll-progress`
- Status: beta
- Categories: navigation
- File: the install writes `scroll-progress.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/menu`. The install adds them.
- Gallery: https://arbokui.com/components/scroll-progress
- Registry item: https://arbokui.com/r/scroll-progress.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/scroll-progress
   ```

## Use it for

- Keep a reader's position visible in an article or long guide.
- Pass sections in reading order, with ids matching mounted elements.
- Track the window by default. Pass containerRef or containerId for an independent scroll surface.
- Place the indicator outside the scrolling content. Use className='absolute' inside a positioned preview wrapper.

## Avoid it for

- Task completion or uploads. Use Progress for work with a known completion value.
- Short content with no useful section jumps. Keep the page simple.
- Replacing visible headings. The menu supplements the document's structure.

## Props

- `sections`: `readonly { id: string; label: string }[]`. Default `[]`. Unique section ids and labels in reading order. An empty list shows a disabled pill.
- `containerRef`: `RefObject<HTMLElement | null>`. A mounted scrolling element. Takes precedence over containerId. Omit both to track the window.
- `containerId`: `string`. The id of a mounted scrolling element, for composition without a client ref. A missing container does not track the window.
- `offset`: `number`. Default `24`. Pixels below the scroll surface's top edge at which a section becomes current. Also leaves space above a jump target.
- `disabled`: `boolean`. Default `false`. Stops the menu from opening. Reading progress continues to update.
- `aria-label`: `string`. Default `"Sections"`. Names the menu. The trigger also includes the current section and the percentage read.
- `className`: `string`. Classes for the root, merged last. Fixed at bottom center by default. Override placement for an embedded scroll surface.

## Accessibility

- The trigger exposes its expanded state, current section, and percentage read. Progress is not a live announcement on every scroll.
- Enter, Space, or Arrow Down opens the menu. Arrow keys, Home, End, and typing move between sections.
- Escape closes the menu and restores trigger focus. Clicking outside also closes it.
- The active section carries aria-current='location' and a filled row.
- Jump scrolling and transitions follow the theme's motion setting, including reduced motion.
- The caller supplies unique ids, meaningful labels, and semantic headings in the reading content.

## Examples

### Track a page

```tsx
import { ScrollProgress } from "@/components/ui/scroll-progress";

export default function Guide() {
  return (
    <>
      <ScrollProgress sections={[{ id: "overview", label: "Overview" }, { id: "details", label: "Details" }]} />
      <section id="overview"><h2>Overview</h2><p>Start with the essentials.</p></section>
      <section id="details"><h2>Details</h2><p>Explore the next steps.</p></section>
    </>
  );
}
```

### Track a scroll container

```tsx
"use client";
import { useRef } from "react";
import { ScrollProgress } from "@/components/ui/scroll-progress";

export default function ReadingPanel() {
  const containerRef = useRef<HTMLDivElement>(null);
  return (
    <div className="relative">
      <div ref={containerRef} className="h-96 overflow-y-auto pb-20" tabIndex={0} role="region" aria-label="Guide">
        <section id="start" className="min-h-96"><h2>Getting started</h2></section>
        <section id="next" className="min-h-96"><h2>Next steps</h2></section>
      </div>
      <ScrollProgress className="absolute" containerRef={containerRef} sections={[{ id: "start", label: "Getting started" }, { id: "next", label: "Next steps" }]} />
    </div>
  );
}
```
