Hint Ladder

A progressive hint affordance for lesson and quiz primitives. Each tap of the reveal button surfaces the next rung in the ladder — nudge first, narrow next, specific last. The ladder is sequentially locked so a learner cannot jump to the near-answer rung without spending the gentler rungs first; the cost forces deliberate thought before asking for help.

Preview
Customize
Options

Installation

npx shadcn@latest add https://craftbits.dev/r/hint-ladder.json

Usage

import { HintLadder, type HintLadderHint } from "@craft-bits/core";
 
const HINTS: ReadonlyArray<HintLadderHint> = [
  { id: "nudge", level: "nudge", text: "Which pointer restores the invariant?" },
  { id: "narrow", level: "narrow", text: "The right pointer grew the window. Which side should shrink?" },
  { id: "specific", level: "specific", text: "Contract from the left while the sum exceeds the target." },
];
 
<HintLadder hints={HINTS} defaultRevealedLevels={0} />

Controlled — lift the count into your own state to drive scoring or analytics:

const [revealed, setRevealed] = useState(0);
 
<HintLadder
  hints={HINTS}
  revealedLevels={revealed}
  onRevealedLevelsChange={(next, level) => {
    setRevealed(next);
    trackHintReveal(level);
  }}
  scored
/>

Anatomy

  • Revealed rung list — top of the ladder. Each rung shows its level label (Nudge / Narrow / Specific) and the hint body. Already-revealed rungs stay visible so the learner can re-read earlier hints.
  • Reveal button — a small lightbulb pill with the next rung's progress counter. Hides itself once every rung has been revealed.
  • Cost chip — an optional "−5%" annotation surfaced via the scored prop. Purely presentational; the parent owns any score deduction.
  • Exhausted marker — a faint "All N hints revealed" caption replaces the button once the ladder is fully spent.

Props

PropTypeDefaultDescription
hintsReadonlyArray<HintLadderHint>Ordered list of progressive hints. Each rung needs id, level, and text.
revealedLevelsnumberControlled count of revealed rungs. Pair with onRevealedLevelsChange.
defaultRevealedLevelsnumber0Uncontrolled starting count.
onRevealedLevelsChange(next: number, level: HintLadderLevel) => voidFires whenever the learner reveals another rung.
scoredbooleanfalseSurface a faint "−5%" cost chip on the reveal button. Presentational only.
initialRevealLabelReactNode"Need a hint"Label rendered before any rung is revealed.
nextRevealLabelReactNode"Next hint"Label rendered after the first rung.
accentColorstringwarning tokenOverride accent color for the rung labels and reveal-button glyph.
classNamestringMerged onto the root via cn().

Accessibility

  • The wrapper exposes role="group" and a default aria-label of "Progressive hint ladder" so screen readers can navigate the ladder as a unit.
  • Each newly revealed rung is announced via aria-live="polite" so screen readers convey the rung as it appears without interrupting in-flight speech. Already-revealed rungs flip to aria-live="off" so they are not re-announced on subsequent reveals.
  • The reveal button enforces a 44 px minimum hit area and shows a visible :focus-visible ring keyed to the accent token.
  • The button's aria-label follows a "Reveal hint N of M — Level" template so screen readers convey progress and rung semantics even when the visible label is a generic prompt.
  • Color is never the only channel — the rung-level label (Nudge / Narrow / Specific) is rendered as text on every revealed rung.
  • Animations respect prefers-reduced-motion via Motion's reduced-motion handling — rungs cross-fade instantly via the same SPRINGS.snap transition.

Credits

  • Extracted from: AlgoFlashcards (src/lessons/primitives/interaction/HintLadder.tsx). Dropped the per-distractor map, the lesson-runtime playSound import, the workbench ControlSchema, and the RichText body in favour of a plain ReactNode. Lifted the counter into a controlled + uncontrolled API following the Radix pattern, replaced the project token helpers with the --cb-warning token plus a generic accentColor override, and routed transitions through SPRINGS.snap.