Power-User Systems

Rewind

An undo history you can scrub, and that refuses to throw the future away. Drag the head and the interface reverts live under your finger rather than on release; act from a past point and the abandoned future stays drawn on the rail as a branch you can walk back onto, instead of being silently destroyed the way every real undo stack destroys it. Controlled in the same shape as Ledger — it owns the rail, the gesture and the branch graph, and your reducer owns the states.

Install

bun add motion clsx tailwind-merge

Then copy 2 files into your project:

  • components/registry/rewind/rewind.tsx
  • lib/utils.ts

Props

PropTypeDefaultDescription
orientation"horizontal" | "vertical""horizontal"Which axis the rail runs on. Horizontal reads as a scrubber; vertical reads as a commit log and stacks better beside a form. The gesture is written against one axis abstraction rather than two handlers, so the modes cannot drift apart.
scrubSnapbooleantrueLock the head to the nearest recorded state while dragging. Off, it moves continuously and the surface shows the state at or before it, which makes the density of the history visible — you can feel where you were working fast.
branchingbooleantrueDraw the futures you abandoned instead of discarding them. Off is the familiar destructive undo; on is the honest model, and the difference between the two is the whole component.
branchDepthnumber3How many abandoned branches stay drawn beside the trunk before the oldest is folded away. Past about four the rail stops being a timeline and becomes a graph, which is a different component.
stiffnessnumber420Spring tension on the head. The marker and the reported index come off the same drag, so the surface can never show a state the head has not reached — a readout running ahead of its own scrubber is the single thing that makes time travel feel fake.
dampingnumber34Spring friction. Low lets the head ring past a state and come back, which is charming on a scrubber and disastrous on the fields it drives — this is the control that decides which of those you get.
tickDensity"sparse" | "even" | "all""even"How many states the rail actually draws. All is truthful and unreadable past about sixty entries; even samples at a fixed pitch and leaves the true count in the label instead.
showLabelsbooleantrueThe action name and position under the rail. Without it a scrub is a position; with it, it is a history you can read while dragging.
railHeightnumber4Thickness of the rail, and of every branch beside it. Thin reads as a scrubber under a document; thick reads as the primary control on the screen.
accentColorstring (hex)"#a855f7"The trunk, the head, and the travelled portion.
branchColorstring (hex)"#f0a830"Abandoned futures. Deliberately a warm off-accent rather than a dimmed trunk — a discarded future is a different kind of thing from a quiet one, not a fainter version of the same thing.

What It Demonstrates

Drag
Moving an element by grabbing it, often with momentum when released.
Interruptible animation
An animation that can be smoothly redirected mid-flight instead of finishing first.
Spatial consistency
Animating so an element keeps its identity and position across states, so users never lose track of where things went.
Continuity transition
A change that keeps the user oriented by visually connecting before and after. For example, making the same rectangle bigger and smaller.
Stepped animation
An animation that is divided into discrete steps, like a countdown timer.
Spring
Motion driven by physics (tension, mass, damping) rather than a set duration.

Related Components

Appears In