Guess Gap Reveal
Guess-the-gap reveal — drag a slider or draw a trend line to guess the answer first, then let go and watch the real data animate in, with a color block marking the gap between "your guess" and the truth alongside the distribution of other readers' guesses.
npx shadcn@latest add https://webberui.com/r/guess-gap-reveal.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<GuessGapReveal />
Installation
npx shadcn@latest add https://webberui.com/r/guess-gap-reveal.jsonOr, once registries are configured in components.json, install it as @webberui/guess-gap-reveal.
Usage
"Guess first, then reveal" is a classic device in data-visual storytelling: making readers commit to a guess and feel the gap with the truth first-hand makes it stick far better. This component offers two input styles, each its own named export.
Guess a value with a slider (GuessGapReveal)
Suited to questions about a single number (a proportion, an amount, a vote count, and so on). Drag the dot or use the arrow keys to pick a guess, and once you press reveal the marker for the real answer surfaces, with a hatched color block marking the gap between the two. Pass crowd and a histogram of other readers' guesses grows in at the reveal.
import { GuessGapReveal } from "@/components/ui/guess-gap-reveal";
<GuessGapReveal
question="What share of people do you think drink coffee every day?"
answer={64}
min={0}
max={100}
suffix="%"
defaultGuess={40}
crowd={[24, 27, 30, 31, 33, 35, 40, 45]}
onReveal={({ gap }) => console.log("gap", gap)}
/>Draw a trend line (GuessTrendReveal)
Suited to questions about a trajectory. Given a known historical segment known and a future segment unknown to be guessed (carrying the real value), the reader drags each handle to draw the trend they expect; at the reveal the real line is drawn with a stroke animation, the area between the two lines is filled with a color block marking the gap, and the mean absolute error is reported.
import { GuessTrendReveal } from "@/components/ui/guess-gap-reveal";
<GuessTrendReveal
known={[
{ label: "'19", value: 42 },
{ label: "'20", value: 39 },
{ label: "'21", value: 47 },
{ label: "'22", value: 58 },
]}
unknown={[
{ label: "'23", value: 71 },
{ label: "'24", value: 66 },
{ label: "'25", value: 88 },
]}
suffix="k"
/>GuessGapReveal Props
| Prop | Type | Default | Description |
|---|---|---|---|
question | React.ReactNode | — | The question text, shown at the very top |
answer | number | — | The real answer value |
min | number | 0 | Minimum of the scale |
max | number | 100 | Maximum of the scale |
step | number | 1 | Drag and keyboard increment |
defaultGuess | number | midpoint of the scale | Initial guess value in uncontrolled mode |
crowd | number[] | — | Other readers' guesses; when provided, a distribution histogram is shown after the reveal |
format | (value: number) => string | thousands separators + suffix | Value formatting function |
suffix | string | "" | Value suffix (such as % or people) |
onReveal | (r: GuessGapResult) => void | — | Fires at the reveal, returning { guess, answer, gap } |
duration | number | 0.9 | Duration of the number roll at the reveal (seconds) |
GuessTrendReveal Props
| Prop | Type | Default | Description |
|---|---|---|---|
known | GuessTrendDatum[] | — | Known historical data points; the line starts from this segment |
unknown | GuessTrendDatum[] | — | The data points to be guessed, carrying the real value as the answer |
domain | [number, number] | derived automatically from the data | Y axis range |
height | number | 220 | SVG height (px, including the label strip at the bottom) |
format | (v: number) => string | thousands separators + suffix | Value formatting function |
suffix | string | "" | Value suffix |
onReveal | (r: GuessTrendResult) => void | — | Fires at the reveal, returning { guesses, answers, meanAbsError } |
duration | number | 1 | Duration of the real line's stroke drawing (seconds) |
GuessTrendDatum is { label: string; value: number }.
How it works
- Two-stage state machine: before
revealedonly the guess updates; afterward the input locks and the reveal animation plays. Pressing "guess again / draw again" returns to the guessing stage to start over. - Gap color block: the slider version grows a hatched color block between the two markers; the trend version forms a closed polygon out of "the guess line outbound plus the real line inbound" and fills it, showing the over- and under-estimated areas at a glance.
- Crowd distribution: the slider version's
crowdis binned into a histogram that grows bin by bin at the reveal, with the bin containing the real answer darkened, making it easy to compare "where most people guessed" against "where the truth is". - Purely neutral colors: the whole component uses only
neutral-*and itsdark:variants, distinguishing "your guess" from "the truth" through solid versus hollow, solid versus dashed, and light versus dark rather than relying on an extra palette.
Accessibility
- The guess handle is
role="slider"witharia-valuemin/max/now/valuetext; arrow keys are supported for fine adjustment (the slider version also hasPageUp/PageDownandHome/End), and in the trend version each handle can be focused independently and adjusted with the up and down keys. - The reveal result is announced through a status region with
aria-live="polite", so screen reader users also learn the real value and the gap. - With "reduce motion" enabled, the number roll, the growing color block, and the line stroke drawing all present instantly, with the functionality and the information completely unchanged.
Isotype Proportion Grid
An "X in every Y" isotype proportion grid — hundreds of small icons light up in a color wave as you scroll, stepping through different proportion statements one at a time.
Radar Skill Chart
Multi-axis radar chart: a drawing animation on reveal, values on vertex hover, and two series overlaid for comparison.