Vector Visualizer

A square SVG plane that renders N labelled 2D vectors from a tail point (default: the origin) to a head (x, y) in math coordinates (y points up). Each vector carries a semantic tone, and the whole set can be made draggable — the foundation primitive for any linear-algebra or vector-arithmetic demo.

Vector A: (3.0, 2.0), magnitude 3.6. Vector B: (-2.0, 3.0), magnitude 3.6. Vector C: (1.0, -3.0), magnitude 3.2.ABC
Customize
Vectors
3
Display

Installation

npx shadcn@latest add https://craftbits.dev/r/vector-visualizer.json

Usage

import { VectorVisualizer } from "@craft-bits/core";
 
<VectorVisualizer
  vectors={[
    { x: 3, y: 2, label: "A", tone: "accent" },
    { x: -2, y: 3, label: "B", tone: "success" },
  ]}
/>

Make it interactive — each arrow head becomes a drag handle. Pair interactive with onVectorsChange to receive the updated array:

const [vectors, setVectors] = useState<Vector2D[]>([
  { x: 3, y: 2, label: "A", tone: "accent" },
  { x: -2, y: 3, label: "B", tone: "success" },
]);
 
<VectorVisualizer
  vectors={vectors}
  interactive
  onVectorsChange={setVectors}
/>

Draw a vector that doesn't start at the origin by setting tail:

<VectorVisualizer
  vectors={[
    { x: 2, y: 2, label: "u" },
    { x: 4, y: 3, tail: { x: 2, y: 2 }, label: "v" },
  ]}
/>

Understanding the component

  1. Math-space coordinates, y points up. Vectors are passed in math space (positive y is up) and the component converts to SVG coordinates internally. The viewport is a square plane with the origin centered — width and height share the same range.
  2. Auto-derived scale. The visible domain range (default [-5, 5]) feeds the px-per-unit scale. Changing size rescales the SVG without changing the visible math span.
  3. Five semantic tones. default / accent / success / warning / error map to var(--cb-fg), var(--cb-accent), var(--cb-success), var(--cb-warning), var(--cb-error). Arrow markers are generated per tone in the SVG <defs> so every head matches its stroke.
  4. Optional grid + axes. showGrid paints integer-spaced gridlines in var(--cb-border-muted); showAxes paints x and y axes through the origin in var(--cb-fg-muted). Toggle either off for a clean canvas.
  5. Drag affordance on the head. When interactive, an invisible 32px-diameter circle on top of each arrow head acts as a WCAG-compliant hit target. Dragging projects the pointer through the SVG's getScreenCTM().inverse() so it tracks correctly under any transform; the head clamps to range.
  6. Keyboard parity. The drag handle is a role="slider" — focus it and use Arrow keys (Shift+Arrow for a 1-unit step) to nudge the head.
  7. Spring transitions. Heads animate with SPRINGS.snap from @craft-bits/core/motion. prefers-reduced-motion: reduce collapses the spring to duration: 0.

Props

PropTypeDefaultDescription
vectorsreadonly Vector2D[]requiredVectors to render. Index drives identity.
rangereadonly [number, number][-5, 5]Visible math-space domain on both axes.
showGridbooleantruePaint the integer-spaced grid.
showAxesbooleantruePaint the x / y axes through the origin.
interactivebooleanfalseMake each arrow head a drag + focus handle.
onVectorsChange(next: Vector2D[]) => voidFires on drag / keyboard nudge with the updated array.
sizenumber320SVG side length in pixels (the plane is square).
transitionTransitionSPRINGS.snapSpring for vector-head transitions.
classNamestringMerged onto the root <div> via cn().

The Vector2D shape:

FieldTypeDefaultDescription
xnumberrequiredHead x in math coordinates.
ynumberrequiredHead y in math coordinates.
labelstringOptional label rendered near the head.
tone"default" | "accent" | "success" | "warning" | "error""default"Visual tone for stroke + arrow.
tail{ x: number; y: number }{ x: 0, y: 0 }Tail point. Defaults to the origin.

Accessibility

  • The SVG is role="img" with a visually hidden summary listing every vector's coordinates and magnitude.
  • When interactive, each arrow head exposes a role="slider" invisible 32px hit target meeting WCAG 2.5.8 (Target Size, Enhanced).
  • Keyboard: focus a handle, then / / / nudge in 0.25-unit steps. Shift+Arrow nudges by 1 unit. The head clamps to range.
  • Animation respects prefers-reduced-motion: reduce — vector-head transitions collapse to an instant swap.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/math/VectorVisualizer.tsx). The source was a dot-product lesson with explore / predict / challenge modes, narration heuristics, and presets baked in. The library extract is the pure visualization primitive — N vectors, optional drag, no lesson scaffolding.