Prediction Gate

A predict-before-reveal quiz primitive. Renders a prompt, a vertical option list, and a reveal button. While predicting, the picker is interactive and reveal stays disabled. After reveal, the picker locks, the correct row is painted success, the wrong-and-picked row is painted error, and a feedback message fades in.

Different from OptionPicker (a stateless multi-choice) — PredictionGate is the flow: pick, commit, see.

Before scrolling on — what do you think 6 × 7 evaluates to?

Customize
Quiz state
42

Installation

npx shadcn@latest add https://craftbits.dev/r/prediction-gate.json

Usage

import { PredictionGate } from "@craft-bits/core";
 
const [value, setValue] = useState<string | null>(null);
const [revealed, setRevealed] = useState(false);
 
<PredictionGate
  prompt="What does 6 x 7 equal?"
  options={[
    { value: "0",  label: "0" },
    { value: "1",  label: "1" },
    { value: "21", label: "21" },
    { value: "42", label: "42" },
  ]}
  correctValue="42"
  value={value}
  onValueChange={setValue}
  revealed={revealed}
  onRevealedChange={setRevealed}
  aria-label="Multiplication prediction"
/>

Uncontrolled — drop value / onValueChange and supply defaultValue (or omit). Same for revealed / defaultRevealed:

<PredictionGate
  prompt="What does 6 x 7 equal?"
  options={options}
  correctValue="42"
  aria-label="Multiplication prediction"
/>

Understanding the component

  1. Two phases. A data-state attribute drives the surface — "predicting" until the user commits via the reveal button, "revealed" after.
  2. Picker reuse. Internally renders OptionPicker in vertical orientation so it inherits the picker's a11y for free — radiogroup, roving tabindex, arrow / Home / End nav, tap scale.
  3. Locked on reveal. Once revealed, the picker receives disabled and aria-disabled="true" is announced on every option.
  4. Reveal badges. A sibling absolute layer renders a small pill (Correct / Your pick) next to each row. The badges are aria-hidden because the same info lands in the feedback paragraph, which is aria-live="polite".
  5. Action affordance. The reveal button is a LessonButton.Pill with tone="accent" and stays disabled until at least one option is picked, so the gate's job-to-be-done is enforced at the affordance level.
  6. Controlled + uncontrolled. Both value and revealed accept controlled props plus uncontrolled default* variants.

Props

PropTypeDefaultDescription
promptReactNoderequiredThe question text rendered above the options.
optionsPredictionGateOption[]requiredAnswer options. Order is preserved.
correctValuestringrequiredThe option value considered correct.
valuestring | nullControlled selection. Pair with onValueChange.
defaultValuestring | nullnullUncontrolled initial selection.
onValueChange(value: string | null) => voidFired when the student picks (or unpicks) an option.
revealedbooleanControlled reveal flag.
defaultRevealedbooleanfalseUncontrolled initial reveal state.
onRevealedChange(revealed: boolean) => voidFired when the reveal button is pressed.
feedback{ correct?: ReactNode; incorrect?: ReactNode }Custom reveal-state messages.
revealLabelReactNode"Reveal answer"Reveal button label.
aria-labelstringAccessible name for the form group.
classNamestringMerged onto the rendered root <div>.

PredictionGateOption

FieldTypeDescription
valuestringStable identifier — drives selection and correctValue match.
labelReactNodeVisible label for the option.

Accessibility

  • Root is a <div role="form"> with the consumer's aria-label. The prompt, picker, feedback, and reveal action live inside one named region.
  • The option list inherits OptionPicker's a11y: role="radiogroup" containing role="radio" buttons with aria-checked, aria-disabled, and data-state. Arrow / Home / End keys move and select; Tab is roving.
  • After reveal, the picker becomes disabled so every option reports aria-disabled="true".
  • The feedback paragraph is aria-live="polite" — the correct / incorrect message is announced without yanking focus.
  • Visual reveal badges are aria-hidden="true" since the same outcome lands in the live region.
  • The reveal button stays disabled until at least one option is picked. AT users both see and are told the action is unavailable until commitment.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/interaction/PredictionGate.tsx). The original carried lesson-specific gating, sound effects, and a hint ladder. craft-bits ships the predict-before-reveal flow as a generalized, accessible, themable primitive.