Bit Set Circuit
A self-contained <svg> that draws a combinational boolean circuit. Pass an array of inputs (each a 0 or 1) and a list of ops, where every op is one of AND, OR, XOR, NOT with operand indices into the merged [...inputs, ...opResults] stream. The component evaluates the circuit bottom-up, lays out one switch column on the left, one gate per op in the middle, and a single output port on the right.
Wires carrying a 1 light up in the track accent; wires carrying a 0 sit at the muted foreground tone. When onInputToggle is provided, each switch becomes a 44px hit target wired to Space / Enter; otherwise the picture renders read-only.
Installation
npx shadcn@latest add https://craftbits.dev/r/bit-set-circuit.jsonUsage
import { BitSetCircuit } from "@craft-bits/core";
<BitSetCircuit
inputs={[1, 0, 1]}
ops={[
{ kind: "AND", a: 0, b: 1 },
{ kind: "NOT", a: 2 },
{ kind: "XOR", a: 3, b: 4 },
]}
/>;Interactive — parent owns the input bits, child fires onInputToggle on every flip:
const [inputs, setInputs] = useState([1, 0, 1]);
<BitSetCircuit
inputs={inputs}
ops={ops}
onInputToggle={(i, next) => {
const copy = inputs.slice();
copy[i] = next;
setInputs(copy);
}}
/>;Custom labels and a hex accent (enables alpha-suffix tinting on the gate fills):
<BitSetCircuit
inputs={[1, 1, 0]}
ops={[{ kind: "OR", a: 0, b: 1 }]}
inputLabels={["lo", "mid", "hi"]}
accentHex="#4A7BF7"
/>Understanding the component
- Stream-indexed operands. Each op's
aandbindex into the concatenated[...inputs, ...opResultsSoFar]. Soa = 0references the first input;a = inputs.lengthreferences the first op's output. This makes gate cascades trivial — opi + 1can read opiby index. - NOT is unary. When
kindisNOT, onlyais read.bis ignored if supplied, so callers can keep a uniform op-builder. - Hot wires light up. Every wire carrying a 1 is painted in the track accent and rendered slightly thicker; wires carrying a 0 sit at a muted foreground tone. The output port mirrors the colour of the final gate.
- Out-of-range indices are safe. If an op references an operand index beyond the current stream, the operand resolves to 0 and the gate still renders — useful while a circuit is being authored.
- Read-only vs. interactive. Omit
onInputToggleto render a static picture; supply it to enable per-switch click-and-keyboard activation with a 44px hit target on each input. - Reduced motion.
usePrefersReducedMotion()collapses every wire-colour spring to instant. Values still update; only the motion drops.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
inputs | readonly number[] | required | Input bits (0/1). Non-zero values are coerced to 1. |
ops | readonly BitSetCircuitOp[] | required | Ordered gate list — { kind, a, b? } per gate. |
inputLabels | readonly string[] | x0, x1, … | Per-input display labels. |
onInputToggle | (index: number, next: number) => void | — | Fires when a switch is clicked or activated. Omit for read-only. |
accentHex | string | var(--cb-accent) | Track accent. Use a #hex to enable alpha-tinted gate fills. |
rowHeight | number | 44 | Row spacing in px between switches. |
columnWidth | number | 88 | Column spacing in px between input column, gate columns, and the output port. |
transition | Transition | SPRINGS.smooth | Override the wire / gate colour transition. |
className | string | — | Merged onto the outer <svg> via cn(). |
Accessibility
- The outer
<svg>isrole="img"with a<title>summarising every input value, every gate, and the final output bit so screen readers can read the circuit state without parsing geometry. - Every interactive switch is
role="switch"witharia-checked, an explicitaria-label,tabIndex={0}, and Space / Enter activation that mirrors the click handler. - Each switch's hit target is at least 44 by 44 px (WCAG 2.5.8 AAA), even though the visible disc is smaller.
- Gates expose
data-kind(AND/OR/XOR/NOT) anddata-state(on/off) on their group element so consumer apps can hook custom styles or assistive tooling. - Motion respects
prefers-reduced-motion: wire-colour, gate fill, and switch scale transitions all snap to instant.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/construction/BitSetCircuit.tsx). The original was a two-phase wire-then-operate lesson scoped to set-membership encoding — it took anelements: string[]payload, drove a workbench reducer + dissectable contract, played sound effects on every toggle, and gated phase one behind a drag-to-wire interaction. The library extract keeps only the visual primitive — input bits, gate list, hot-wire rendering — and lets the caller compose any reducer-driven scoring or wiring puzzle on top.