WebberUI

Chaptered Report Shell

An annual-report / white-paper page shell — a sticky chapter rail generated automatically from the content, giant chapter numbers cross-fading between chapters, full-bleed color-block transitions between sections, and a dual reading-progress indicator.

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/chaptered-report-shell.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.

16
<ChapteredReportShell />

Installation

npx shadcn@latest add "https://webberui.com/r/chaptered-report-shell.json?t=<install token>"

Usage

Drop ReportChapter children straight in, and the chapter rail on the left is generated automatically from their id / title / number. When a chapter number is not specified, they are numbered in order as two digits:

import {
  ChapteredReportShell,
  ReportChapter,
} from "@/components/ui/chaptered-report-shell";

<ChapteredReportShell eyebrow="2025 Annual Report">
  <ReportChapter id="overview" title="Year in Review" kicker="Overview">
    <p>Body copy for chapter one…</p>
  </ReportChapter>
  <ReportChapter
    id="financials"
    title="Financial Performance"
    kicker="Financials"
    accentClassName="bg-indigo-600 dark:bg-indigo-500"
  >
    <p>Body copy for chapter two…</p>
  </ReportChapter>
  <ReportChapter
    id="outlook"
    title="Looking Ahead"
    accentClassName="bg-amber-500"
  >
    <p>Body copy for chapter three…</p>
  </ReportChapter>
</ChapteredReportShell>

When scrolling happens inside a nested overflow container, pass that container's ref to container (both the progress indicators and the transition animations follow it):

const scrollerRef = React.useRef<HTMLDivElement>(null);

<div ref={scrollerRef} className="h-[400px] overflow-y-auto">
  <ChapteredReportShell container={scrollerRef} stickyTop={8}>
    {/* ... */}
  </ChapteredReportShell>
</div>

Props

ChapteredReportShell

PropTypeDefaultDescription
childrenReactNodeDrop ReportChapter children straight in; the rail is generated from their props
containerRefObject<HTMLElement>When scrolling happens inside a nested overflow container, pass that container's ref
eyebrowReactNodeA small heading above the rail, such as "2025 Annual Report"
labelstring"Chapter navigation"accessible label for the rail's <nav>
stickyTopnumber16Offset of the sticky rail and progress bar from the top (px); can be 0 for nested scrolling
classNamestringAppended to the outermost container's className

ReportChapter

PropTypeDefaultDescription
idstringThe chapter anchor id, used by rail clicks and the observer to match chapters
titlestringChapter title, shown on the rail and the transition color block
numberstringnumbered automatically in orderChapter number string; numbered automatically as two digits (01, 02, …) when omitted
kickerReactNodeA category heading at the top left of the transition color block
accentClassNamestringneutral dark backgroundTailwind class for the transition block's background color; must be a solid color that carries white text, such as "bg-indigo-600"
childrenReactNodeChapter body copy
classNamestringAppended to the chapter <section>'s className

How it works

  • The chapter rail is generated entirely from ReportChapter props: there is no separate table of contents to maintain, and adding a chapter makes it appear on the rail automatically
  • The current chapter is determined by an IntersectionObserver (with the scroll container as root) that takes each chapter's visible proportion and picks the highest, so switching never skips
  • The giant chapter numbers cross-fade between chapters with AnimatePresence: the old number drifts up and fades out while the new one floats in from below and un-blurs
  • Each chapter opens with a full-bleed transition color block that wipes in from the left (scaleX) on entering the viewport, with the number and title floating out afterwards
  • Dual reading-progress indicators: a top progress bar tied to scrollYProgress, plus a reading cursor sliding along the rail's track with markers lighting up chapter by chapter
  • Rail items are real <button> elements that smooth-scroll to their chapter on click and mark the current position with aria-current="location"; the current chapter is also indicated by font weight and color, not by a colored dot alone
  • On narrow windows the rail collapses automatically (using @container to judge the container width), leaving the content column unaffected
  • When the user has "reduce motion" enabled at the system level: the color blocks appear directly, numbers switch instantly, progress tracks without spring smoothing, and rail clicks scroll instantly — with the layout and content completely unchanged

On this page