WebberUI

Callout Stat Hero

A hero layout built around one giant number filling the first screen, with SVG leader lines drawn one by one as you scroll, connecting parts of the number to surrounding annotation cards that surface in sequence.

Loading preview…
npx shadcn@latest add https://webberui.com/r/callout-stat-hero.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.

<CalloutStatHero />

Installation

npx shadcn@latest add https://webberui.com/r/callout-stat-hero.json

Or, once registries are configured in components.json, install it as @webberui/callout-stat-hero.

Usage

import { CalloutStatHero } from "@/components/ui/callout-stat-hero";

<CalloutStatHero
  eyebrow="2025 customer satisfaction"
  value="94%"
  caption="Share of surveyed customers who would recommend us"
  annotations={[
    { x: 8, y: 34, side: "left", title: "5 quarters of growth in a row" },
    { x: 20, y: 76, side: "left", title: "12,480 responses" },
    { x: 92, y: 30, side: "right", title: "NPS 62" },
    { x: 82, y: 78, side: "right", title: "First response < 2h" },
  ]}
/>

By default the whole window is the scroll container (an h-screen stage). If you put it inside a nested overflow-y-auto container, pass that container's ref to container and use stageClassName to override the stage height to the container's visible height:

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

<div ref={scrollerRef} className="h-[480px] overflow-y-auto">
  <CalloutStatHero
    value="94%"
    annotations={annotations}
    container={scrollerRef}
    stageClassName="h-[480px]"
  />
</div>

Props

PropTypeDefaultDescription
valueReact.ReactNodeThe giant number that fills the first screen
eyebrowReact.ReactNodeThe small label above the number
captionReact.ReactNodeThe description text below the number
annotationsStatAnnotation[]The list of annotations surrounding the number, drawn and surfaced in sequence as you scroll
connector"straight" | "elbow""straight"Leader line style: a straight line, or an elbow with a horizontal lead-in segment
scrollScalenumberestimated from the annotation countHeight of the scroll track as a multiple of the pinned stage height
offsetTopnumber0Sticky top position of the pinned stage (px)
containerRefObject<HTMLElement | null>Ref of the nested scroll container; the window is the scroll container by default
stageClassNamestring"h-screen"Class of the pinned stage container; override it to the container's visible height when nesting inside a scroll container
numberClassNamestringOverrides the class of the giant number (size, weight, color, …)
aria-labelstring"數據主視覺"Accessible name of the whole section — the built-in default is Traditional Chinese for "data hero", so pass this prop to localize it

StatAnnotation

FieldTypeDefaultDescription
xnumberHorizontal position of the anchor (percentage of the number box width, 0–100)
ynumberVertical position of the anchor (percentage of the number box height, 0–100)
titleReact.ReactNodeAnnotation card title
descriptionReact.ReactNodeAnnotation card body (optional)
side"left" | "right"determined automatically from xWhether the card docks to the left or right column
idstringindexCustom React key

How it works

  • The section pins the whole stage with position: sticky, with the outer track height stretched by scrollScale, so the scroll distance is the progress source for drawing the leader lines one by one.
  • The leader line endpoints are measured from the DOM after mount (midpoint of the card's inner edge → the number anchor), tracked through reflows with ResizeObserver, and converted to stage coordinates for drawing, which is why the text always stays crisp and is never blurred by scaling.
  • The lines are drawn with pathLength, and the small anchor circle pops in only once that line has finished drawing, while the annotation card surfaces with opacity and a slight offset, producing a "draw the line → light the point → present the card" rhythm.
  • x / y are percentages relative to the number element's bounding box, so they can point precisely at a particular character or corner; leave margin for anchors near 0 or 100 so they are not clipped by the overflow-hidden stage.

Accessibility

  • With "reduce motion" enabled at the system level, it renders a static summary instead, with no scrolling and no leader lines: the giant number plus a numbered list of the annotations, complete and readable.
  • The leader line svg, the anchors, and the small start circles are all marked aria-hidden so they do not interfere with assistive technology; the number and the annotation text are announced normally.
  • The stage container carries role="group" and an aria-label, so it can be navigated as a single identifiable section.

On this page