A time machine for forms: wraps any region, records every change (grouped typing, checkboxes, selects, contenteditable) as a labeled undo tree (“Address changed”, “Shipping method: Express”), shown as a compact timeline. Dragging along the timeline rewinds the fields live, with a ghost halo on whatever differs from now and the current value as a tag; “Restore this state” commits, and abandoned versions stay reachable as branches. Ctrl/⌘ Z and Shift Z, optional session persistence.
Position of the bar relative to the wrapped content.
debounce
number (ms)
700
Quiet time before typing is recorded (checkboxes, radio buttons and selects are recorded immediately; leaving a field forces a record).
merge
number (ms)
4000
Successive edits to the same field within this window form a single entry; going back to the previous value removes the entry.
max
number
120
Maximum number of entries kept (the oldest are pruned).
keyboard
region | document | off
region
Ctrl/⌘ Z undoes, Ctrl/⌘ Shift Z or Ctrl Y redoes: when focus is inside the region, anywhere on the page (except other fields), or never. Inside the region, the component’s history replaces native per-field undo.
persist
string
Keeps the history in sessionStorage under this key (empty value: element id or page path) and replays it on reload.
initial-label
string
Initial state
Label of the first entry.
data-rewind-label
string
On a field or a group (fieldset…): fixed entry label, “{value}” is replaced with the new value. Otherwise: “Label: “value””.
data-rewind-name
string
On a field: short name used in automatic labels.
data-rewind-ignore
boolean
On a field or a container: never recorded.
Properties
Name
Type
Description
history
{ label, time, current }[]
Entries on the displayed line (read-only).
previewing
boolean
True while previewing the past.
Methods
Name
Description
undo() / redo()
Steps back / forward one entry (values written with input and change events).
preview(index)
Previews entry number index (0 = the oldest), without committing anything.
restore(index?)
Commits the current preview (or entry index).
cancel()
Leaves the preview and returns to the present.
snapshot({ label?, time? })
Records an entry now (time lets you rebuild an existing history).
clear({ label?, time? })
Forgets the history and starts again from the current state.
Events
Name
Description
mv-snapshot
New entry. detail: { label, count }.
mv-preview
An entry is previewed. detail: { label, time, differences: string[] }.
mv-restore
A past state became the current state. detail: { reason: "restore" | "undo" | "redo", label, time, state }.
Content structure
Name
Description
(content)
Any region: form, fieldset, contenteditable area. Fields added later are picked up.
CSS classes
Name
Description
mv-rewind-bar
Generated bar: title, timeline, undo / redo, preview tray.
mv-rewind-track / -tick / -thumb / -branch
Timeline (role slider), entry ticks, thumb and branches for abandoned versions.
mv-rewind-halo / -now
Ghost halo set on a field that differs from the present, and its “Current” tag.
CSS variables
Name
Default
Description
--mv-rewind-accent
var(--mv-accent)
Color of the present (fill, thumb).
--mv-rewind-past
var(--mv-warning)
Color of the past: halos, preview, the portion that would be undone.
--mv-rewind-bg
Bar background.
Accessibility
The timeline is a role="slider": arrows, Home/End and Page Up/Down browse the history in preview, Enter restores, Escape returns to the present; aria-valuetext gives the label and age (“Address changed, 15 minutes ago”). During a preview, the region is inert (nothing can be changed by mistake) and a live region announces how many fields differ; restore, undo and redo are announced too. Branches are real, labeled buttons. Restored values go through native setters and fire input/change (React, Vue… stay in sync). Passwords, credit card fields (autocomplete cc-*), one-time codes and files are never recorded. Reduced motion: no thumb glide or field shake. No loops: everything is event-driven.