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.jsonUsage
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
- Two phases. A
data-stateattribute drives the surface —"predicting"until the user commits via the reveal button,"revealed"after. - Picker reuse. Internally renders
OptionPickerin vertical orientation so it inherits the picker's a11y for free — radiogroup, roving tabindex, arrow / Home / End nav, tap scale. - Locked on reveal. Once revealed, the picker receives
disabledandaria-disabled="true"is announced on every option. - Reveal badges. A sibling absolute layer renders a small pill (
Correct/Your pick) next to each row. The badges arearia-hiddenbecause the same info lands in the feedback paragraph, which isaria-live="polite". - Action affordance. The reveal button is a
LessonButton.Pillwithtone="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. - Controlled + uncontrolled. Both
valueandrevealedaccept controlled props plus uncontrolleddefault*variants.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
prompt | ReactNode | required | The question text rendered above the options. |
options | PredictionGateOption[] | required | Answer options. Order is preserved. |
correctValue | string | required | The option value considered correct. |
value | string | null | — | Controlled selection. Pair with onValueChange. |
defaultValue | string | null | null | Uncontrolled initial selection. |
onValueChange | (value: string | null) => void | — | Fired when the student picks (or unpicks) an option. |
revealed | boolean | — | Controlled reveal flag. |
defaultRevealed | boolean | false | Uncontrolled initial reveal state. |
onRevealedChange | (revealed: boolean) => void | — | Fired when the reveal button is pressed. |
feedback | { correct?: ReactNode; incorrect?: ReactNode } | — | Custom reveal-state messages. |
revealLabel | ReactNode | "Reveal answer" | Reveal button label. |
aria-label | string | — | Accessible name for the form group. |
className | string | — | Merged onto the rendered root <div>. |
PredictionGateOption
| Field | Type | Description |
|---|---|---|
value | string | Stable identifier — drives selection and correctValue match. |
label | ReactNode | Visible label for the option. |
Accessibility
- Root is a
<div role="form">with the consumer'saria-label. The prompt, picker, feedback, and reveal action live inside one named region. - The option list inherits
OptionPicker's a11y:role="radiogroup"containingrole="radio"buttons witharia-checked,aria-disabled, anddata-state. Arrow / Home / End keys move and select; Tab is roving. - After reveal, the picker becomes
disabledso every option reportsaria-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
disableduntil 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.