Activation Match Inline

ActivationMatchInline is a compact drag-and-drop quiz that pairs deep-learning scenarios (a 96-layer transformer, a shallow CNN, a binary classifier, a multi-class head) with the activation each should ship with (GELU, ReLU, Sigmoid, Softmax). The learner drags an activation chip into a scenario card — or taps a chip to auto-place it into the first empty row — and once every row has an assignment, the Check answers button reveals correctness with a per-row pedagogical explanation.

The deck is data-driven: pairs defines the rows, the chip pool is derived from those rows (or supplied explicitly via activationChoices), and per-chip colors come from activationColors with a sensible default that maps the canonical four activations onto the --cb-info / --cb-accent / --cb-warning / --cb-success semantic tokens.

96-layer transformer
GPT-scale, deep feedforward blocks
6-layer CNN
Image classifier, wide channels
Binary output
"Is this spam?" — one probability
Multi-class output
"Which digit 0–9?" — probability distribution
GELU
ReLU
Sigmoid
Softmax
0 of 4 assigned.
Customize
Deck
4 of 4
Layout

Installation

npx shadcn@latest add https://craftbits.dev/r/activation-match-inline.json

Usage

import { ActivationMatchInline } from "@craft-bits/viz/activation-match-inline";
 
<ActivationMatchInline />

Override the deck with your own pairs:

<ActivationMatchInline
  pairs={[
    {
      id: "rnn",
      scenario: "Recurrent layer in an LSTM",
      detail: "Long sequence, gated cell state",
      correctActivation: "Tanh",
      explanation:
        "Tanh keeps the cell state in (-1, 1) so it doesn't explode through time.",
    },
    {
      id: "gate",
      scenario: "Forget gate output",
      detail: "Per-step retention probability",
      correctActivation: "Sigmoid",
      explanation:
        "Sigmoid emits a (0, 1) gate — exactly the right shape for a per-step retention probability.",
    },
  ]}
  activationColors={{
    Tanh: "var(--cb-accent)",
    Sigmoid: "var(--cb-warning)",
  }}
/>

Listen for the score on submit:

<ActivationMatchInline
  onCheck={({ score, total }) =>
    analytics.track("activation_match", { score, total })
  }
/>

Anatomy

  1. Two interaction modes per chip. Activation chips can be dragged into any scenario card's drop zone, or clicked / Enter-pressed to auto-place into the first unassigned row. Reassigning an already-used chip moves it from its old row to the new one — there's always at most one chip per row and at most one row per chip.
  2. Always-visible drop zones. Unassigned scenarios render a "drop here" affordance that subtly tints when a chip is being dragged anywhere in the component. This keeps the drop target obvious without requiring a hover-over to surface it.
  3. Reveal-on-Check. While revealed is false, the cards stay neutral and the chips stay interactive. Once Check Answers fires, every assigned card grows a 2px ring (--cb-success for correct, --cb-error for wrong) and an AnimatePresence height-tween reveals the explanation block below. The chips become read-only.
  4. Semantic color, never color-only. Correctness is also surfaced through the textual "correct" / "answer: <activation>" header and the score readout — color is supplementary, not the only signal.
  5. Reduced-motion respected throughout. Under prefers-reduced-motion: reduce, the chip pop-in, the explanation height tween, the action-row enter / exit, and the chip hover/tap scales all collapse to zero-duration transitions.

Props

PropTypeDefaultDescription
pairsreadonly ActivationMatchInlinePair[]ACTIVATION_MATCH_INLINE_DEFAULT_PAIRSMatch rows. Each row's correctActivation must equal one of the available chips.
activationChoicesreadonly string[]derived from pairsThe chip pool. When omitted, falls back to the unique correctActivation values in input order.
activationColorsReadonly<Record<string, string>>ACTIVATION_MATCH_INLINE_DEFAULT_COLORSMap of activation name → CSS color. Missing entries fall back to --cb-accent.
captionstringOptional caption rendered under the action row.
onAssignmentsChange(next) => voidCalled whenever an assignment changes.
onCheck({ score, total, assignments }) => voidCalled when Check Answers is tapped.
onReset() => voidCalled when Try Again is tapped.
classNamestringMerged onto the root via cn().

Accessibility

  • The scenarios live inside a role="list" / role="listitem" pair; the chip pool is a role="group" with an aria-label describing the interaction model.
  • Each chip is a focusable role="button" with an aria-label of the form "\<activation\> — drag to a scenario or press Enter to assign". Enter / Space triggers the auto-place behaviour. Used chips become aria-disabled and lose tab-stoppability.
  • Each scenario card is aria-labelledby its scenario title and, once revealed, aria-describedby the explanation block.
  • An off-screen aria-live="polite" region announces progress ("N of M assigned") and the final score ("Score N of M") so screen-reader users get parity with the visual state.
  • Color is never the only correctness signal: every revealed row also carries the textual "correct" / "answer: <activation>" header, and the score readout switches between --cb-success and --cb-warning plus the literal N/M number.
  • Motion respects prefers-reduced-motion: reduce — the chip pop-in, the explanation height tween, the action-row enter / exit, and the chip hover/tap scales all collapse to instant transitions.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/ActivationMatchInline.tsx). The source pulled inline --color-bar-4 / --color-accent-400 / --color-warn-400 / --color-success-400 / --color-fail-400 / --color-ink-* / --color-surface-elevated / --color-accent-500 palette references and a SPRINGS.snug spring. The viz extract re-keys them onto the --cb-* semantic-token vocabulary, swaps the spring for SPRINGS.snap from @craft-bits/core/motion, wraps everything in forwardRef + cn() + ...props spread, adds an activationColors knob, exposes onAssignmentsChange / onCheck / onReset callbacks, narrows the drag MIME from text/plain to application/x-cb-activation-match (with a text/plain fallback), and adds full reduced-motion handling plus a polite live region.