Boundary Markers
A row of cells with lo and hi pointer markers (and an optional mid probe) drawn on top. The alive range — cells whose index lies inclusively between the two pointers — carries an accent ring and full opacity; the eliminated halves dim to half opacity. As the caller drives the search forward, the pointer labels tween between positions with a single critically-damped spring. The component owns layout and motion; the caller owns the search.
BoundaryMarkers is the "shape" of binary search. No search logic lives here — pass length plus the current pointers on every render and the primitive draws the rest. Drop it into any binary-search narrative — lower-bound, upper-bound, "find peak", "search rotated", or any other narrowing-range algorithm.
Installation
npx shadcn@latest add https://craftbits.dev/r/boundary-markers.jsonUsage
import { BoundaryMarkers } from "@craft-bits/core";
const length = 12;
const [lo, setLo] = useState(0);
const [hi, setHi] = useState(length - 1);
const mid = hi >= lo ? Math.floor((lo + hi) / 2) : undefined;
<BoundaryMarkers length={length} lo={lo} hi={hi} mid={mid} />;Hide the mid probe — useful when the caller is showing the pointers between iterations:
<BoundaryMarkers length={12} lo={3} hi={9} />Show array values inside the cells instead of indices:
<BoundaryMarkers
length={8}
lo={2}
hi={6}
mid={4}
values={[1, 3, 5, 7, 9, 11, 13, 15]}
/>Custom labels and tones — for lower / upper bound search variants where the pointers carry different meanings:
<BoundaryMarkers
length={10}
lo={0}
hi={9}
mid={4}
loLabel="lower"
hiLabel="upper"
midLabel="probe"
loTone="success"
hiTone="error"
midTone="warning"
/>Understanding the component
- Cell row. Every cell is rendered left to right at indices
0throughlength - 1. Cell width uses a responsiveclamp()formula so the row shrinks on narrow viewports while capping atcellSizeon wider ones — the same pattern asArrayCellsandStringCells. - Alive vs. eliminated. A cell whose index lies in the inclusive range between
loandhiis alive: full opacity, accent ring. A cell outside that range is eliminated: half opacity, faint border. The transition between alive and eliminated tweens the ring colour and opacity, so eliminations read as a wash rather than a hard cut. - Pointer labels. The pointer lane above the cells is a row of one label slot per cell. Labels mount inside the slot owning their pointer index; when an index changes,
AnimatePresenceslides the old label out and the new one in. Multiple pointers can land on the same cell (lo === hiat the end of a search), in which case the labels stack vertically. - Mid emphasis. When
midis set, the mid cell gets a second accent ring in themidTonecolour on top of the alive treatment — easy to spot during the probe. - Empty range. When
hiis less thanlo, the alive range is empty and every cell renders eliminated. The accessible summary reports "alive range empty". - Reduced motion. When
prefers-reduced-motion: reduceis set, pointer transitions and cell tints snap to instant.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | required | Total number of cells in the range. |
lo | number | required | Lower-bound marker index. |
hi | number | required | Upper-bound marker index. |
mid | number | — | Optional mid probe index. |
values | readonly (string | number)[] | — | Values rendered inside the cells. Defaults to the index. |
showIndices | boolean | true | Render numeric indices below each cell. |
loLabel | string | "lo" | Label for the lo pointer. |
hiLabel | string | "hi" | Label for the hi pointer. |
midLabel | string | "mid" | Label for the mid pointer. |
loTone | BoundaryMarkerTone | "accent" | Tone for the lo pointer. |
hiTone | BoundaryMarkerTone | "accent" | Tone for the hi pointer. |
midTone | BoundaryMarkerTone | "warning" | Tone for the mid pointer. |
cellSize | number | 44 | Ideal cell size in px. |
compact | boolean | false | Smaller cells, tighter row. |
transition | Transition | SPRINGS.smooth | Pointer / cell transition. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The outer container is
role="img"with anaria-labelsummarising the current state (for example, "Boundary markers: length 12, alive range 3..9, mid at 6."). - The pointer label lane is decorative (
aria-hidden="true"); the canonical state is in the summary so screen readers read it once per render instead of three times. - Alive vs. eliminated cells layer ring colour + ring width + opacity, never colour alone — the distinction stays legible for colour-blind users.
- Motion respects
prefers-reduced-motion: reduce— pointer transitions and cell tints collapse to instant.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/decision/BoundaryMarkers.tsx). The original source was a 1000-line lesson teaching difference arrays through manual range updates, marker placement, and a prefix-sum sweep. The library extract drops the lesson chrome, the reducer, the audio, and the hint bar; what remains is the geometric primitive that sits underneath every binary-search-style algorithm — a range with two boundary pointers and an optional probe — generalised to a controlledlengthplusloplushiplusmidAPI on the Radix pattern.