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.jsonThis 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
- Per-line shiki tokens.
codeToTokensruns insideuseEffectand stores a 2D array —lines[i]is the token list for linei. Each token renders as a<span>with its shiki-resolvedcolor. Until shiki resolves, plain-text<code>keeps the same monospace stack and padding, so there's no layout shift on resolve. - Spring-animated highlight bar. A
motion.divis absolutely positioned behind the code withyset to(activeLine - 1) × LINE_HEIGHT_PX.SPRINGS.smoothtweensyso the bar glides between rows.AnimatePresencehandles fade in/out whenactiveLineisnull. - Inline annotations. Pass
annotations={{ lineNumber: ReactNode }}— the annotation for the active line expands below the line via anAnimatePresenceheight tween. Inactive-line annotations are not rendered, so the cost is bounded by the visible callout. - Dim siblings. When a line is active, non-active lines fade to 40% opacity via a CSS opacity transition — purely cosmetic, no remounts.
- Reduced motion. When
prefers-reduced-motion: reduceis set, the highlight bar's tween collapses toduration: 0(the bar snaps to position) and the annotation expansion runs with no spring.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
code | string | required | The code string to render. |
lang | 'tsx' | 'ts' | 'jsx' | 'js' | 'py' | 'bash' | 'tsx' | Shiki language id. |
activeLine | number | null | — | Controlled 1-indexed active line. null clears the highlight. |
defaultActiveLine | number | null | null | Uncontrolled initial active line. |
onActiveLineChange | (line: number) => void | — | Fires when a line is clicked or activated via keyboard. |
annotations | Record<number, ReactNode> | — | 1-indexed map of per-line annotation content. |
showLineNumbers | boolean | true | Render the 1-indexed line-number gutter. |
theme | ThemeRegistration | neutral | Override the shiki theme. |
className | string | — | Merged onto the root <div>. |
Accessibility
- The code region is wrapped in
aria-live="polite"so screen readers announce the new active line. - When
onActiveLineChangeis provided, each line is arole="button"withtabIndex={0}, anaria-labelof"Line N"(annotated lines append"(annotated)"), andaria-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 genericannotationsmap and an opt-inonActiveLineChangecallback. The highlight bar is now driven byvar(--cb-accent)instead of a per-instance hex.