Stack Viz
A vertical LIFO stack visualization. Items are accepted bottom-to-top — items[0] is the deepest cell, items.at(-1) is the top. Pushes slide in from above; pops fade up out of view. The topmost item is accent-tinted and labelled with a floating ← top by default.
16
9
4
1
Customize
Stack
4
← top
Installation
npx shadcn@latest add https://craftbits.dev/r/stack-viz.jsonUsage
import { StackViz } from "@craft-bits/core";
<StackViz items={[1, 4, 9, 16]} />Animate pushes and pops by updating the items array — the component diffs by tail-position and animates the difference:
const [stack, setStack] = useState<number[]>([1, 4, 9]);
<StackViz items={stack} />
<button onClick={() => setStack((s) => [...s, s.length + 1])}>push</button>
<button onClick={() => setStack((s) => s.slice(0, -1))}>pop</button>Understanding the component
- Bottom-to-top input, top-to-bottom render. The
itemsarray is read withitems[0]at the bottom — the natural way to think about a LIFO. Internally the array is reversed for render so the latest push sits at the top of the DOM, where it visually lands. - Top item is the protagonist. The cell at
items.at(-1)is tinted with--cb-accent, gets a soft accent shadow, carriesdata-state="top", and renders the floatingtopLabelto its right. Every other cell uses--cb-bg+--cb-border-muted. - Push / pop motion. Cells live inside
<AnimatePresence initial={false}>. New cells enter withopacity: 0, y: -cellSize/2, scale: 0.96— they slide down from above. Popped cells exit with the mirrored transform so they read as "lifting off the top."SPRINGS.smoothdrives both directions. - Reduced motion. When
prefers-reduced-motion: reduceis set, the spring collapses toduration: 0and items snap. - Overflow. Without
maxHeight, the column grows freely. WithmaxHeight, the inner region becomes scrollable so excess items remain reachable. - Screen-reader announcement. The stack body is
role="list"witharia-live="polite", and itsaria-labelis rebuilt on every render ("Top of stack: 16. 4 items."), so SR users hear what changed at the top after a push or pop.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | readonly (string | number)[] | required | Stack contents, bottom-to-top. The last element is the top. |
cellSize | number | 40 (or 32 when compact) | Pixel height of each cell, also used as floor min-width. |
compact | boolean | false | Tighter gaps + smaller default cell. |
topLabel | string | "← top" | Text rendered beside the top item. Pass an empty string to suppress. |
maxHeight | number | — | Cap the visible height in pixels. Overflow becomes scrollable. |
className | string | — | Merged onto the outer <div>. |
Accessibility
- The stack body is
role="list"and every cell isrole="listitem"— assistive tech sees a single list whose order matches the visual order (top first). aria-live="polite"on the stack region announces the new top after any push or pop. The label is built from the top value, not the position.- The top cell carries
data-state="top"so consumers can target it via attribute selectors without parsing classes. - The floating
topLabelisaria-hidden— the live region already names the top, so duplicating it would announce twice. - Motion respects
prefers-reduced-motion: enter / exit transitions collapse toduration: 0so the layout snaps.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/viz/StackViz.tsx). The original primitive bundled scan-trail highlighting, multi-stacklayoutIdmorphing, and a per-itemhexoverride targeted at AF's track-color system. The library extract is the LIFO primitive — the lesson-specific affordances live in the source project.