Undo and Redo
Hyperclay apps come with real undo and redo. While you are editing a page, Cmd+Z steps backward through your changes and Cmd+Shift+Z steps forward, the same way you would expect in any desktop app. The model is simple: the page is the state, and undo moves between page states. Structural edits, attribute changes, and text changes are all recorded automatically, no code required.
It just works in the right preset
Undo ships in the smooth-sailing and everything presets. Load one of those and undo starts itself the moment you enter edit mode:
<script src="https://cdn.jsdelivr.net/npm/hyperclayjs@latest/src/hyperclay.js?preset=smooth-sailing" type="module"></script>A visitor viewing the page never loads undo, it only runs in edit mode for the owner. If you build a custom feature list instead of using a preset, add &features=undo.
Shortcuts
| Action | Keys |
|---|---|
| Undo | Cmd+Z (Ctrl+Z on Windows) |
| Redo | Cmd+Shift+Z (Ctrl+Shift+Z) |
| Redo (Windows) | Ctrl+Y |
If your cursor is inside an embedded code or rich-text editor (CodeMirror, Monaco, Ace, Quill, Tiptap, ProseMirror), Cmd+Z is left to that editor’s own undo instead of the page’s, so editing inside a widget feels normal.
What undo covers
Recorded automatically, with no author code:
- Adding, removing, moving, and reordering elements
- Attribute changes
- Text changes
- Edits made through the CMS
- Inputs marked with
[persist]
When undo removes an element, it puts back the very same element, so its event handlers, focus, and scroll position survive the trip.
The one gap: plain inputs
Typing into a plain <input> or <textarea> does not create the kind of change undo watches for, so a bare input is invisible to the page’s undo stack. Because the global Cmd+Z is in charge while you edit, pressing it can feel like it skips your typing. Three ways to handle it, pick whichever fits:
- Add
[persist]to the input. Persisted inputs mirror their value into the DOM, which undo does see, and you get persistence for free as well. - Wrap the change in a step yourself (see below) if the value is set by your own code.
- Opt out of the key binding with
bindKeys: falseif you would rather keep the browser’s native, per-character input undo on that page.
Make a custom action undoable
If your own code changes the page and you want it to land as one undo step, wrap it in commit:
hyperclay.undo.commit('Add row', () => {
table.append(newRow)
})Everything the function does becomes a single step labeled Add row. Rapid changes that you do not wrap are coalesced into one step automatically after a short idle pause, so a burst of edits does not flood the history. Saving the page does not clear undo, your history survives a save.
To make a value written by code undoable, record it explicitly:
input.value = 'hello'
hyperclay.undo.recordValue(input)Build your own undo UI
Everything is on hyperclay.undo. Read the state to render buttons, and listen for changes to keep them in sync:
const { canUndo, canRedo, history } = hyperclay.undo
hyperclay.undo.on('commit', () => updateButtons())
hyperclay.undo.on('undo', () => updateButtons())
hyperclay.undo.on('redo', () => updateButtons())
undoButton.onclick = () => hyperclay.undo.undo()
redoButton.onclick = () => hyperclay.undo.redo()canUndo and canRedo are booleans, and history is the list of steps (each with a label and timestamp), oldest first. The events are undo, redo, commit, and clear.
Limits
- History does not survive a page reload. A fresh load starts empty.
- There is no shared or collaborative undo. Changes that arrive from another tab or another person through live-sync are deliberately kept out of your local stack, so your Cmd+Z never reverts someone else’s work.
- History holds about the last 100 steps.
The full reference for every method and option lives at hyperclayjs.com , and the LLM-ready version is at hyperclayjs.com/llms.txt .