Skip to main content

Keyboard Shortcuts

<AmnesiaShortcuts /> is the only built-in keyboard binding. Drop one inside an AmnesiaProvider and the app gets standard undo / redo chords:

ChordAction
Ctrl+Z / Cmd+ZUndo
Ctrl+Shift+Z / Cmd+Shift+ZRedo
Ctrl+YRedo
import { AmnesiaProvider, AmnesiaShortcuts } from "react-amnesia";

export function App({ children }: { children: React.ReactNode }) {
return (
<AmnesiaProvider>
<AmnesiaShortcuts />
{children}
</AmnesiaProvider>
);
}

Skipping native editables

By default, chords originating from a native editable element are ignored. This includes text-like <input> types (for example text, email, search, tel, url, password, and number), plus <textarea>, <select>, and contenteditable. The browser's own undo handles those, and stealing the chord usually breaks user expectations. The check walks event.composedPath() so editables inside an open shadow root (Lit / web components) are also recognized.

If you want to override that — e.g. inside a canvas region with no native editables — opt out:

<AmnesiaShortcuts skipEditableTargets={false} />

Native menu integration

For Electron/Tauri edit menus (or any non-keyboard "Undo" trigger), use react-amnesia/native:

import { dispatchNativeUndo, isNativeEditableElement } from "react-amnesia/native";

function handleUndoMenuClick() {
if (isNativeEditableElement(document.activeElement)) {
if (dispatchNativeUndo("undo")) return;
}
// Otherwise call your app-level amnesia undo.
}

For a full end-to-end desktop-shell setup (native menu creation, enabled state syncing, DevTools registry wiring), see OS Menu Integration (Tauri/Electron).

Disabling globally

For a modal that owns its own undo:

<AmnesiaShortcuts enabled={modalIsOpen ? false : true} />

This is preferable to unmounting the component — toggling enabled detaches and re-attaches the listener cleanly.

Pinning to a target

target accepts:

  • An HTMLElement | Document | Window
  • The string aliases "document" or "window" (SSR-safe — they resolve inside useEffect)
  • null to disable the listener entirely
<AmnesiaShortcuts target="document" />

Pinning to a scope

When the provider has multiple scopes (multi-scope routing), the chord routes to the active scope by default. Pin it to a specific scope with scopeId:

<AmnesiaShortcuts scopeId="canvas" target={canvasRef.current} />

Why preventDefault always fires

When the chord matches and shortcuts are not skipped, event.preventDefault() fires whether or not an entry was actually undone. This is required because async undo/redo cannot synchronously decide whether to suppress the browser's native chord. If you want native fallback for empty stacks, set preventDefault={false}.

Skipping when defaultPrevented

Chords whose event.defaultPrevented === true are ignored — an upstream handler already claimed them. Alt-modified chords are also ignored (Ctrl+Alt+Z is not Undo).

See also