WebberUI

Running Head Folio

A book running-head system — a sticky thin bar showing the current chapter title on the left and a page number derived from the scroll position on the right, with the chapter title flipping like a turning page and the folio incrementing in step at chapter boundaries.

Loading preview…
npx shadcn@latest add https://webberui.com/r/running-head-folio.json

Playground

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

1
40
300
<RunningHeadFolio />

Installation

npx shadcn@latest add https://webberui.com/r/running-head-folio.json

Or, once registries are configured in components.json, install it as @webberui/running-head-folio.

Usage

Wrap a number of RunningHeadFolioChapter elements in RunningHeadFolio; each chapter's title becomes the chapter name on the running-head bar. If the reading area is a nested scroll container, pass the container ref to container.

import {
  RunningHeadFolio,
  RunningHeadFolioChapter,
} from "@/components/ui/running-head-folio";

export function Reader() {
  const scrollerRef = React.useRef<HTMLDivElement>(null);

  return (
    <div ref={scrollerRef} className="h-[70vh] overflow-y-auto">
      <RunningHeadFolio container={scrollerRef} center="progress" startPage={1}>
        <RunningHeadFolioChapter title="Prologue">
          {/* chapter content */}
        </RunningHeadFolioChapter>
        <RunningHeadFolioChapter title="Chapter One">
          {/* chapter content */}
        </RunningHeadFolioChapter>
      </RunningHeadFolio>
    </div>
  );
}

center switches the middle field: "progress" shows a thin reading-progress line, "title" shows bookTitle, and "none" leaves it blank. The page number's presentation can be customized with format, for example as Roman numerals:

<RunningHeadFolio
  container={scrollerRef}
  center="title"
  bookTitle="Notes on Kinetic Typography"
  format={(p) => `p. ${p}`}
>
  {/* … */}
</RunningHeadFolio>

Props

RunningHeadFolio

PropTypeDefaultDescription
childrenReactNodePut RunningHeadFolioChapter children here
containerRefObject<HTMLElement | null>Ref of the nested scroll container; defaults to the window as the scroll container
center"title" | "progress" | "none""progress"Style of the middle field
bookTitleReactNodeThe book title in the middle (used when center="title")
startPagenumber1Starting page number
pageHeightnumberone screen height of the containerScroll distance corresponding to one page (px)
barHeightnumber40Height of the running-head bar (px)
format(page: number) => ReactNode(p) => pCustomize how the page number is presented
classNamestringClass for the outer container
barClassNamestringClass for the running-head bar (can override the background color)

RunningHeadFolioChapter

PropTypeDefaultDescription
titleReactNodeChapter name, shown on the running-head bar
childrenReactNodeChapter content
idstringAnchor id for the chapter container
classNamestringClass for the chapter container

How it works

  • Page number derivation: the useScroll progress is mapped onto [startPage, startPage + total pages − 1]; the total page count comes from the total content height divided by pageHeight, and when pageHeight is unspecified it takes one screen height of the scroll container, so "one screen is roughly one page".
  • Current chapter detection: the bottom edge of the running-head bar is the baseline, and the last chapter whose top has crossed that baseline is the current one, so the chapter title switches exactly in step with the reading position.
  • Flip direction: the flip direction recognizes the scroll direction — when incrementing downward the new value flips in from below, and when going back upward it flips away in reverse.
  • Adaptivity: a ResizeObserver watches the content and container size, recomputing the total page count and the current chapter whenever the size changes.

Accessibility

  • The running-head bar is marked role="status" with an aria-label, containing a plain-text summary (which chapter, which page) for assistive technology to announce; the visually duplicated flip content is marked aria-hidden so nothing is announced twice.
  • Each chapter is a native section, and a string title becomes its aria-label.
  • When "reduce motion" is enabled at the system level, the page-flip animation becomes a very short fade, and all other information and layout are fully preserved.

On this page