Keyboard shortcuts
Bind keys to intents or commands from your application.
Bind application shortcuts to an Intent, a CommandId, or a DOM runtime action. The runtime already owns native editing input: typing, delete, paragraph breaks, paste, cut, copy, IME composition, selection sync, pointer placement, and drag selection. Host shortcuts should cover product actions such as opening a link dialog, toggling a sidebar, applying a saved style, or invoking a custom command.
Examples assume an engine and mounted runtime from Quickstart.
const host = runtime.rootElement;
Rules of thumb
- Let the DOM runtime handle native editing keys while host code owns product shortcuts.
- Call
event.preventDefault()only after you have matched the shortcut. - Dispatch a semantic intent or command from the shortcut.
- Prefer a command when the same behavior is used by keyboard, toolbar, menu, or plugin code.
- Prefer
runtime.invokeAction(...)when the action already exists in the runtime catalog.
Bind a key to an intent
host.addEventListener("keydown", (event) => {
const isMod = event.metaKey || event.ctrlKey;
if (!isMod || event.shiftKey || event.altKey) return;
if (event.key.toLowerCase() !== "k") return;
event.preventDefault();
engine.dispatch({
kind: "setLink",
href: "https://spineeditor.com",
});
});
This path is useful when the host already has the exact semantic edit. The intent still goes through planning, transaction apply, normalization, commit, history, render, and selection projection.
Bind a key to a custom command
Register a CommandId once, invoke it from any UI surface.
import {
createCommandRegistry,
createEditingCommands,
createHistoryCommands,
extendCommandRegistry,
type CommandId,
type NodeTypeName,
} from "@spine-editor/core";
const SetTitleHeading = "setTitleHeading" as CommandId;
const commandRegistry = extendCommandRegistry(
createCommandRegistry()
.registerMany(createHistoryCommands())
.registerMany(createEditingCommands()),
[
{
command: {
id: SetTitleHeading,
run() {
return {
kind: "setSelectedBlockType",
toType: "heading" as NodeTypeName,
attrs: { level: 1 },
};
},
},
},
],
);
// Pass commandRegistry to createEditorEngine, then:
host.addEventListener("keydown", (event) => {
const isMod = event.metaKey || event.ctrlKey;
if (!isMod || !event.altKey) return;
if (event.key !== "1") return;
event.preventDefault();
engine.dispatchCommand(SetTitleHeading);
});
run() returns an Intent or null. The engine dispatches the returned intent.
Invoke a DOM runtime action
For toolbar-equivalent shortcuts, use the action catalog instead of duplicating enabled and active state:
host.addEventListener("keydown", (event) => {
const isMod = event.metaKey || event.ctrlKey;
if (!isMod || event.shiftKey || event.altKey) return;
if (event.key.toLowerCase() !== "b") return;
event.preventDefault();
runtime.invokeAction("inline.bold");
});
Action ids are exposed by runtime.getActions(). Host UI can render from that metadata so readonly and selection-aware action state stays in one place.
Map several shortcuts to runtime actions
const shortcutActions = new Map([
["mod+b", "inline.bold"],
["mod+i", "inline.italic"],
["mod+shift+7", "block.list.ordered"],
["mod+shift+8", "block.list.bullet"],
["mod+alt+2", "block.heading.2"],
]);
function shortcutId(event: KeyboardEvent): string | null {
const isMod = event.metaKey || event.ctrlKey;
if (!isMod) return null;
return [
"mod",
event.altKey ? "alt" : null,
event.shiftKey ? "shift" : null,
event.key.toLowerCase(),
]
.filter(Boolean)
.join("+");
}
host.addEventListener("keydown", (event) => {
const actionId = shortcutActions.get(shortcutId(event) ?? "");
if (actionId === undefined) return;
const action = runtime.getActions().find((item) => item.id === actionId);
if (action?.enabled !== true) return;
event.preventDefault();
runtime.invokeAction(actionId);
});
Built-in command ids
import { CoreCommandIds } from "@spine-editor/core";
engine.dispatchCommand(CoreCommandIds.undo);
engine.dispatchCommand(CoreCommandIds.redo);
engine.dispatchCommand(CoreCommandIds.selectAll);
The built-in command set covers deletion, paragraph breaks, paste text, marks, links, block type, block alignment, list commands, media row commands, select all, undo, and redo. Commands produce intents or invoke engine-owned history operations; the engine applies the resulting transaction.
Keep native editing keys with the runtime
Plain Backspace, Delete, Enter, printable keys, Tab, selection arrows, and paste/copy/cut shortcuts belong to the runtime substrate. Product shortcuts should sit above that layer and call commands or runtime actions after a precise match.