caliper uidocs

Scroll story

Block

Sections that change as you scroll.

Installs components/ui/scroll-story.tsxDepends on none beyond ReactMotion honours reduced motion
pnpm dlx shadcn@latest add https://caliperui.site/r/scroll-story.json
  1. 01

    Write the change

    Work in a branch. Every component ships with its states, its keyboard behaviour and its tests.

  2. 02

    Check it everywhere

    Light and dark, desktop and phone, motion on and off. The same checks run on every pull request.

  3. 03

    Ship it calmly

    A release is one button with a checklist behind it. If anything is in the way, it says exactly what.

When to use #

Use it for

  • Walking through a product story.

Skip it when

  • Short pages.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/scroll-story.json

Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

example.tsx
import { ScrollStory } from "@/components/ui/scroll-story"

const mock = (n: number, label: string) => (
  <div className="grid w-full max-w-xs gap-2" aria-hidden="true">
    <p className="text-xs uppercase tracking-wide text-muted-foreground">{label}</p>
    {[0.9, 0.6, 0.75].map((w, i) => <span key={i} className="block h-3 rounded-full" style={{ width: `${w * 100}%`, background: `color-mix(in srgb, var(--chart-${n}) ${70 - i * 18}%, var(--background))` }} />)}
    <span className="mt-2 block h-16 rounded-lg" style={{ background: `color-mix(in srgb, var(--chart-${n}) 22%, var(--background))` }} />
  </div>
);

export function ScrollStoryDemo() {
  return (
    <div className="grid gap-3">
    <ScrollStory label="How a release goes out" steps={[
              { id: "s1", title: "Write the change", body: "Work in a branch. Every component ships with its states, its keyboard behaviour and its tests.", visual: mock(1, "Branch") },
              { id: "s2", title: "Check it everywhere", body: "Light and dark, desktop and phone, motion on and off. The same checks run on every pull request.", visual: mock(2, "Checks") },
              { id: "s3", title: "Ship it calmly", body: "A release is one button with a checklist behind it. If anything is in the way, it says exactly what.", visual: mock(4, "Release") },
            ]} />
    </div>
  );
}

API reference #

ScrollStory

component
PropTypeDefault
steps *readonly StoryStep[]—
labelstring"Story"
classNamestring—

Accessibility #

It is built on native elements and keeps aria-label, aria-hidden and aria-current in step with state.

KeyBehaviour
↓Moves to the next item or value.
↑Moves to the previous item or value.

Motion #

It checks prefers-reduced-motion itself and settles immediately instead of travelling. Every state is still reachable.