String Cells

A monospace grid that renders one character per cell — the canonical view for sliding-window, two-pointer, palindrome, and substring algorithms. Sibling of ArrayCells, specialized for strings: whitespace shows as a visible escape glyph, the font is locked to --cb-font-mono, and per-cell styles model the four states most string algorithms touch.

String of 7 characters.
0
L
1
2
3
4
R
5
6
Customize
String
racecar
50%
Cell
36px

Installation

npx shadcn@latest add https://craftbits.dev/r/string-cells.json

Usage

import { StringCells } from "@craft-bits/core";
 
<StringCells
  value="racecar"
  pointers={[
    { index: 0, label: "L", tone: "accent" },
    { index: 6, label: "R", tone: "accent" },
  ]}
/>

Highlight the active window of a sliding-window scan:

<StringCells
  value="abcabcbb"
  pointers={[
    { index: 2, label: "L", tone: "success" },
    { index: 5, label: "R", tone: "warning" },
  ]}
  cellStyles={{
    0: "dimmed",
    1: "dimmed",
    2: "highlight",
    3: "highlight",
    4: "highlight",
    5: "highlight",
  }}
/>

Understanding the component

  1. One cell per code point. Array.from(value) splits the string by Unicode code points so emoji and combining characters get one cell each instead of breaking into surrogate halves.
  2. Whitespace is visible. Space renders as , tab as , and newline as . The escape glyph paints in --cb-fg-subtle italics so the visual weight differs from real characters.
  3. Pointers above, indices below. Pointer labels animate above their cell via AnimatePresence mode="popLayout"; the numeric index strip below uses tabular-nums so digits don't wobble as labels change.
  4. Per-cell style states. Each cell reads cellStyles[i]default, highlight, visited, or dimmed. The style maps to a semantic-token combo, so theming the accent through CSS variables repaints every cell.
  5. Pointer tones. Pointers carry a tone that resolves to the matching --cb-* variable — the same colour adapts across light / dark / branded themes without a code change.
  6. Responsive width. Cell width uses clamp(24px, …, cellSize) so the row shrinks gracefully on narrow viewports and never breaks below the legibility floor.
  7. Reduced motion. When prefers-reduced-motion: reduce is set, the cell colour transition collapses to duration: 0 — state changes snap instead of spring.

Props

PropTypeDefaultDescription
valuestringrequiredThe string to visualize. Split per Unicode code point.
pointersreadonly StringCellPointer[][]Pointer markers above the cells. Multiple may share an index.
cellStylesRecord<number, StringCellStyle>{}Per-cell visual emphasis: default | highlight | visited | dimmed.
cellSizenumber32Ideal pixel width of each cell (cells shrink to fit narrow viewports).
showIndicesbooleantrueRender the numeric index strip beneath the cells.
caseSensitivebooleantrueWhen false, glyphs are uppercased before rendering (the underlying value is untouched).
classNamestringMerged onto the outer <div>.

Accessibility

  • The outer element is role="figure" with a visually hidden caption announcing the string length.
  • Each cell carries role="img" and an aria-label like "Index 3: c" (or "Index 4: space" for escaped whitespace) so a screen-reader user can read the string character-by-character.
  • Pointer tones derive from semantic-token CSS variables so users with custom palettes get the same contrast.
  • The colour transition between cell states respects prefers-reduced-motion and collapses to an instant swap when the user opts out.
  • Colour is never the only signal — every cell renders its character and a data-state attribute, so styling state stays readable for colourblind users.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/viz/StringCells.tsx). The library version normalises the fixed five-state palette to the four general-purpose states most string-algorithm visualisations need and folds the original sibling PointerLabels component into the file so consumers install a single primitive.