Code Trace

A code block tuned for stepping through an algorithm one line at a time. Each line is shiki-tokenized; an active-line highlight bar springs between rows as activeLine changes. Optional per-line annotations expand below the active line as an inline callout.

Preview
function binarySearch(nums, target) {
  let lo = 0, hi = nums.length - 1;
  while (lo <= hi) {
Loop while the range is non-empty.
    const mid = (lo + hi) >> 1;
    if (nums[mid] === target) return mid;
    if (nums[mid] < target) lo = mid + 1;
    else hi = mid - 1;
  }
  return -1;
}
Customize
Snippet
binary search
Active line
line 3
Features

Installation

npx shadcn@latest add https://craftbits.dev/r/code-trace.json

This pulls code-block as a transitive registry dependency so the shared shiki theme JSON lands in the same install.

Usage

import { CodeTrace } from "@craft-bits/core";
 
const CODE = `function binarySearch(nums, target) {
  let lo = 0, hi = nums.length - 1;
  while (lo <= hi) {
    const mid = (lo + hi) >> 1;
    if (nums[mid] === target) return mid;
  }
  return -1;
}`;
 
<CodeTrace code={CODE} lang="ts" activeLine={3} />

With annotations:

<CodeTrace
  code={CODE}
  lang="ts"
  activeLine={3}
  annotations={{
    3: "While we still have search space, keep halving it.",
    4: "Compute the midpoint with a bit-shift to avoid overflow.",
  }}
/>

Controlled stepping

Pair activeLine with onActiveLineChange to drive the highlight externally (a timeline scrubber, a phase reducer, a play/pause loop):

const [line, setLine] = useState<number>(1);
 
<CodeTrace
  code={CODE}
  lang="ts"
  activeLine={line}
  onActiveLineChange={setLine}
/>

When onActiveLineChange is supplied, every line becomes a keyboard-accessible button — clicking or pressing Enter / Space on a row activates it.

Uncontrolled

Pass defaultActiveLine to seed the initial highlight without owning state:

<CodeTrace code={CODE} lang="ts" defaultActiveLine={1} />

Understanding the component

  1. Per-line shiki tokens. codeToTokens runs inside useEffect and stores a 2D array — lines[i] is the token list for line i. Each token renders as a <span> with its shiki-resolved color. Until shiki resolves, plain-text <code> keeps the same monospace stack and padding, so there's no layout shift on resolve.
  2. Spring-animated highlight bar. A motion.div is absolutely positioned behind the code with y set to (activeLine - 1) × LINE_HEIGHT_PX. SPRINGS.smooth tweens y so the bar glides between rows. AnimatePresence handles fade in/out when activeLine is null.
  3. Inline annotations. Pass annotations={{ lineNumber: ReactNode }} — the annotation for the active line expands below the line via an AnimatePresence height tween. Inactive-line annotations are not rendered, so the cost is bounded by the visible callout.
  4. Dim siblings. When a line is active, non-active lines fade to 40% opacity via a CSS opacity transition — purely cosmetic, no remounts.
  5. Reduced motion. When prefers-reduced-motion: reduce is set, the highlight bar's tween collapses to duration: 0 (the bar snaps to position) and the annotation expansion runs with no spring.

Props

PropTypeDefaultDescription
codestringrequiredThe code string to render.
lang'tsx' | 'ts' | 'jsx' | 'js' | 'py' | 'bash''tsx'Shiki language id.
activeLinenumber | nullControlled 1-indexed active line. null clears the highlight.
defaultActiveLinenumber | nullnullUncontrolled initial active line.
onActiveLineChange(line: number) => voidFires when a line is clicked or activated via keyboard.
annotationsRecord<number, ReactNode>1-indexed map of per-line annotation content.
showLineNumbersbooleantrueRender the 1-indexed line-number gutter.
themeThemeRegistrationneutralOverride the shiki theme.
classNamestringMerged onto the root <div>.

Accessibility

  • The code region is wrapped in aria-live="polite" so screen readers announce the new active line.
  • When onActiveLineChange is provided, each line is a role="button" with tabIndex={0}, an aria-label of "Line N" (annotated lines append "(annotated)"), and aria-current="true" on the active line. Enter and Space both activate the focused line.
  • Focus is visible via a focus-visible: outline keyed to --cb-accent.
  • The line-number gutter is aria-hidden — only the code itself is read aloud.
  • The annotation callout is role="note", attached to the active line in DOM order.
  • The highlight bar is aria-hidden — dimming is cosmetic and screen readers receive the active line through the live region instead.
  • Reduced motion (prefers-reduced-motion: reduce) snaps the highlight bar between rows and collapses the annotation expansion to zero duration.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/viz/CodeTrace.tsx). The library version drops project-specific props (trackHex, variant, lineRules, getLineAction, getLineDecoration) in favor of a generic annotations map and an opt-in onActiveLineChange callback. The highlight bar is now driven by var(--cb-accent) instead of a per-instance hex.