WebberUI

Recap Story Scroll

A full-screen, scene-by-scene scroll-snap narrative scroll — as each scene settles, oversized numbers and keywords are choreographed in, the background color transitions across the whole scene by theme, and progress dots and autoplay are built in.

This is a WebberUI Pro component

Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.

How to install Pro components →See the plans →

Loading preview…
npx shadcn@latest add "https://webberui.com/r/recap-story-scroll.json?t=<install token>"

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

4500
<RecapStoryScroll />

Installation

npx shadcn@latest add "https://webberui.com/r/recap-story-scroll.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/recap-story-scroll.

Usage

Assembled from compound components: RecapStoryScroll is the scroll-snap container, each RecapScene is one scene, and inside a scene you choreograph the data with RecapStat (oversized numbers) and RecapReveal (relay reveals). The container height is set through className, and each scene fills the container's full height.

import {
  RecapStoryScroll,
  RecapScene,
  RecapStat,
  RecapReveal,
} from "@/components/ui/recap-story-scroll";

<RecapStoryScroll autoPlay className="h-[560px]">
  <RecapScene background="#100a24" label="Prologue">
    <RecapReveal>
      <p className="text-7xl font-bold">2025</p>
    </RecapReveal>
  </RecapScene>

  <RecapScene background="#1c0b33" label="Listening">
    <RecapStat value={48210} suffix=" minutes" label="Total listening time" />
    <RecapReveal delay={0.35}>
      <p className="text-sm text-white/70">That is 33 days without a break.</p>
    </RecapReveal>
  </RecapScene>

  <RecapScene background="#0a2418" label="Epilogue">
    <RecapReveal>
      <p className="text-4xl font-bold">Thanks for your 2025</p>
    </RecapReveal>
  </RecapScene>
</RecapStoryScroll>

Props

RecapStoryScroll

PropTypeDefaultDescription
childrenReact.ReactNodePut RecapScene children here, one per scene
autoPlaybooleanfalseWhether to play through the scenes automatically after mount (force-disabled under reduced motion)
autoPlayIntervalnumber4500How long each scene stays during autoplay (milliseconds)
loopbooleantrueWhether to loop back to the first scene after the last one plays
showProgressbooleantrueWhether to show the scene progress dots on the right
showKeywordsbooleantrueWhether to show the keyword relay row at the bottom (taken from each scene's label)
showControlsbooleantrueWhether to show the scene counter and playback controls in the top-left
onSceneChange(index: number) => voidCallback when the settled scene changes
classNamestringContainer styles; used to set the height (h-[560px] by default)

RecapScene

PropTypeDefaultDescription
childrenReact.ReactNodeScene content (RecapStat / RecapReveal or your own nodes)
backgroundstring#0a0a0aBackground color for this scene (any CSS color value); when the scene settles, the whole background transitions smoothly to it
labelstringScene label, used for the progress dot tooltip and the keyword relay at the bottom
classNamestringClass appended to that scene's container

RecapStat

PropTypeDefaultDescription
valuenumberTarget value, counted up from 0 and scaled in when the scene settles
prefixstring""Prefix for the number (for example "$")
suffixstring""Suffix for the number (for example "+" or "%")
decimalsnumber0Number of decimal places
localestring"en-US"Intl.NumberFormat locale, deciding the thousands separator and decimal mark
durationnumber1.4Duration of the count animation (seconds)
labelstringCaption text below the number
classNamestringStyles for the number
labelClassNamestringStyles for the caption text

RecapReveal

PropTypeDefaultDescription
childrenReact.ReactNodeContent to reveal (headings, keywords, explanatory sentences, and so on)
delaynumber0Delay before the reveal starts after the scene settles (seconds), for relaying several passages within one scene
ynumber20Initial vertical offset (px)
classNamestringStyles

How it works

  • Scene-by-scene settling: the container is a snap-y snap-mandatory scroll area with each scene filling its height; a single IntersectionObserver picks whichever scene is closest to the viewport as the settled scene.
  • Data reveal: the components inside a scene read from context whether their scene has settled. RecapStat counts up from 0 and scales in when the scene settles, and resets to zero when it leaves so it can replay; RecapReveal staggers by delay, which is how the keywords relay passage by passage within a single scene.
  • Whole-scene color change: the background color is driven by the settled scene's background, transitioning smoothly on the container's base layer (backgroundColor is interpolatable). This component is an immersive dark canvas, so give each scene a fairly dark background and adjust the text color through className where needed.
  • Autoplay: autoPlay starts after mount and advances one scene every autoPlayInterval; it pauses automatically when the user interacts with the wheel or by touch, and you can also switch scenes by hand with the buttons in the top-left.
  • Keyword relay: the bottom lists each scene's label in order, lighting up the settled scene's keyword and fading the rest, forming a baton-passing index of the narrative.

Accessibility

  • When the user has "reduce motion" enabled at the system level, the scroll-snap layout and the content are kept (everything is shown directly) while the count / scale reveals, the whole-scene color transition, and autoplay are disabled, and jumping between scenes becomes an instant scroll.
  • The scroll area is focusable and supports scene-by-scene navigation with the arrow keys, PageUp, PageDown, Home, and End; any keyboard or mouse interaction pauses autoplay.
  • The progress dots on the right are native buttons that can be focused with the keyboard and clicked to jump scenes, with the settled scene marked aria-current; the play button carries aria-pressed and an aria-label.
  • Each scene carries aria-roledescription="scene" and an aria-label; the counting layer of RecapStat is hidden from assistive technology and announces its final value through aria-label.

On this page