# Scroll rail (goodtabs UI)

A sticky rail of numbered section links with a marker that follows your reading position.

## Install

```bash
npx shadcn@latest add https://ui.goodtabs.co/r/scroll-rail.json
```

Or, with the @goodtabs namespace configured in components.json: `npx shadcn@latest add @goodtabs/scroll-rail`

## Usage

```tsx
import { ScrollRail, ScrollRailItem } from "@/components/ui/scroll-rail"

<ScrollRail>
  <ScrollRailItem id="setup" title="Set up the project" label="Setup">
    <p>One paragraph of the article.</p>
  </ScrollRailItem>
</ScrollRail>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| label | `string` | `"On this page"` | On ScrollRail, names the nav landmark and sits above the rail. On ScrollRailItem, a shorter name for the rail. |
| id | `string` |  | The anchor on ScrollRailItem, required and unique on the page. |
| title | `string` |  | The section heading on ScrollRailItem, required. |
| children | `ReactNode` |  | The ScrollRailItem sections, or a section's prose. |

## When to use

- A long article, guide or changelog read from top to bottom
- A docs page where people jump between sections
- A case study with four to eight named parts

## When not to use

- On a short page, because a rail with two links explains nothing
- For site navigation: this only links inside the page
- For more than about ten sections, where the phone strip gets long

## Notes for agents

- GSAP installs from npm under GSAP's own licence. No GSAP code ships in this registry
- ScrollRailItem must be a direct child of ScrollRail, because the rail is read from its children
- Links are real anchors and the rail is sticky in CSS, so it works with no script. aria-current="location" and the marker need the script
- Below 768px the rail is a sticky top strip with a progress hairline, and the active link scrolls into it
- Under reduced motion the marker still tracks the active section but jumps instead of scrubbing
- It reads window scroll, so it must not sit inside an overflow container

## The maths

- The reading line sits at 40 percent of the viewport and slides to 100 percent over the last 60 percent of a screen of scroll, so the last section always lights up.
- Marker y is rows[i] + t × (rows[i + 1] - rows[i]), where t is how far the line is through section i, so long and short sections both reach the next row exactly on time.
- The rail takes 13rem and the gap 4rem from a 60rem column, leaving 43rem for headings. Prose stops at max-w-xl, 36rem, about 75 characters at 16px.

Docs: https://ui.goodtabs.co/components/scroll-rail