Skip to content

No.208 · Time· New

Undo Tree

Undo that never throws your work away. Every state is drawn as a node on a branching line: change something after undoing and the old future becomes a pencil-grey branch instead of disappearing. Hover any step to preview it, click to restore it, and watch the current-state ring travel the branches to get there.

The prompt

Copy this text

Automatic copying is unavailable. Copy the selected text using your device’s copy command.

Written for the variant you picked above: Light

Build branching undo history for an editor, as a hook plus a view. The hook keeps a tree, not a stack: undo moves to the parent, a change made after undoing becomes a new child (the old future survives as a sibling branch), redo follows whichever child you most recently came back from, and repeated commits with the same label within 900ms merge (so typing a word is one step). It exposes value, commit(value, label), undo, redo, goTo(id), canUndo and canRedo. The view is a 12px-radius white panel with a hairline border, in Geist and Geist Mono. A header row reads 'Headline history' with '9 states · 3 branches' in mono on the right. Below, a scrolling list (340px max) where every state is a 34px row: on the left an SVG lane diagram, on the right a mono step number, the step's label ('Title case', 'Typed', 'Shorten') and, on the current row, a filled accent 'Now' pill; dead-end branch tips get an outlined 'Branch end' pill. Lane diagram: rows run in the order states were created, top to bottom. A node's first child continues straight down its lane; any later child opens a new lane 22px to the right, joined by a line that drops half a row, curves right with 8px corners and drops into its lane. Nodes are 5px circles. The path from the first state to the current one is inked in the accent blue (filled nodes, blue lines, darker row text); every other branch is pencil grey with hollow nodes. A 9px accent ring marks the current state. Under the list a preview strip says 'Hover a step to preview it'; hovering or focusing a row shows that state's content there ('Step 4 · click or Enter to restore') via a renderPreview prop. In the demo it sits beside a headline editor with Title case, Sentence case and Shorten actions and Undo/Redo buttons, seeded with two branches. Selected variant — Light (light): Palette for this theme (Light): --utree-accent #2457d6; --utree-bg #ffffff; --utree-ink #16181d; --utree-line rgb(22 24 29 / 0.1); --utree-muted #6a707c; --utree-pencil #b9bdc6; --utree-row #f2f5fd. Treat these as the root colour tokens, one value per role, and name them to suit your code. Select it with the theme option set to "light". Keep every dimension, spacing value, state and motion from the brief unchanged, and keep text at 4.5:1 contrast or better on these surfaces. This selection takes precedence over references to other themes or options in the general brief. Implement this selected appearance; do not add a variant picker or alternative appearances unless requested. Implementation requirements: Use React and TypeScript. Keep the component self-contained, with uniquely scoped CSS and no animation libraries. Include required imports, styles and font setup in the result. Interaction: Click a row (or press Enter on it) to restore that state; onSelect(id) is wired to goTo. The list is a tree: ↑ goes to the parent, ↓ to the remembered child, ←/→ to sibling branches, Home to the start and End back to the current state; moving focus previews the state. In the demo editor ⌘/Ctrl+Z undoes and ⇧⌘Z or Ctrl+Y redoes. Motion: On every restore, undo or redo the current-state ring travels the actual route, up to the nearest shared step and down the other branch, in up to 900ms with cubic-bezier(.2,.8,.2,1). A new branch draws its connecting line over 360ms. Previews fade up 3px. With reduced motion or motion={false}, the ring jumps and lines appear complete. Accessibility: The rows form a role='tree' of treeitems with aria-level, aria-selected and aria-current on the present state, one tab stop and the arrow model above. The SVG is decorative. Restores are announced ('Restored step 4: Shorten.'). The preview is visual; the restored text itself is the accessible result. Responsive behavior: The panel fills its column and the list scrolls vertically past 340px; the lane diagram's width grows with the number of branches and labels truncate with an ellipsis. Touch: Tap a row to restore it. Hover previews are a bonus, since the restored state is visible immediately and can be undone. Preserve keyboard access, visible focus and reduced-motion behavior. Keep fictional demo data and simulated service calls in a separate usage example; expose callbacks for real actions and recover from rejected requests. Keep usage-example links inside the demo: use local buttons or prevent navigation. Demonstration links must not navigate to Vitrine routes or open other pages; preserve real URL support in the reusable component itself. Verify the result at narrow and wide viewport sizes, with keyboard input and reduced motion enabled.

Notes

Interaction
Click a row (or press Enter on it) to restore that state; onSelect(id) is wired to goTo. The list is a tree: ↑ goes to the parent, ↓ to the remembered child, ←/→ to sibling branches, Home to the start and End back to the current state; moving focus previews the state. In the demo editor ⌘/Ctrl+Z undoes and ⇧⌘Z or Ctrl+Y redoes.
Motion
On every restore, undo or redo the current-state ring travels the actual route, up to the nearest shared step and down the other branch, in up to 900ms with cubic-bezier(.2,.8,.2,1). A new branch draws its connecting line over 360ms. Previews fade up 3px. With reduced motion or motion={false}, the ring jumps and lines appear complete.
Accessibility
The rows form a role='tree' of treeitems with aria-level, aria-selected and aria-current on the present state, one tab stop and the arrow model above. The SVG is decorative. Restores are announced ('Restored step 4: Shorten.'). The preview is visual; the restored text itself is the accessible result.
Responsive
The panel fills its column and the list scrolls vertically past 340px; the lane diagram's width grows with the number of branches and labels truncate with an ellipsis.
On touch
Tap a row to restore it. Hover previews are a bonus, since the restored state is visible immediately and can be undone.

Featured

363 components