---
title: Hotkeys
description: The default keymap, the chord syntax for plugin and app hotkeys, precedence, and how to override or disable a default binding.
icon: keyboard
---

Every key the editor handles goes through one keymap: your app's bindings, then each plugin's in list order, then the built-in ones. This page lists the defaults and shows how to add, override and disable bindings.

In the tables, <kbd>Mod</kbd> is <kbd>Cmd</kbd> on macOS and iOS and <kbd>Ctrl</kbd> elsewhere.

## Default keymap

### Editing and history

The **Command** column names the command `onBeforeOperation` sees first (see [commands and steps](/docs/plugins/operations#commands-and-steps)).

| Keys | Action | Command | Where |
| --- | --- | --- | --- |
| <kbd>Enter</kbd> | Split the block, or add a block before or after it; what it does depends on the block's role, see [Enter and Backspace by role](#enter-and-backspace-by-role) | `splitBlock`, `insertBlockAfter`, `insertBlockBefore`, `addChildBlock`, `unNestBlock` or `setBlock` | Text |
| <kbd>Shift</kbd> + <kbd>Enter</kbd> | Insert a soft line break | `insertText` | Text |
| <kbd>Mod</kbd> + <kbd>Enter</kbd> | Start a new block below, splitting at the end of the caret's text. In a toggle's header, open or close the toggle instead (Notion); the document does not change | `splitBlock` | Text |
| <kbd>Tab</kbd> | Nest the block into its previous sibling; selected sibling blocks, or the blocks a text selection spans, nest together and stay selected. A selection across nesting levels nests each group of sibling blocks one level, as one undo step; a selected block's selected descendants move with it. A closed toggle a block nests into opens, and a moved toggle keeps its open state | `nestBlock`; `moveBlocks` for several | Text, or selected blocks |
| <kbd>Shift</kbd> + <kbd>Tab</kbd> | Move the block out one level, after its parent; the blocks nested after it become its children. Selected sibling blocks, or the blocks a text selection spans, move out together. Across nesting levels each group of sibling blocks moves out one level, a selected block's selected descendants with it; top-level blocks stay. A closed toggle that takes the blocks after it as children opens | `unNestBlock`; `moveBlocks` for several | Text, or selected blocks |
| <kbd>Backspace</kbd>, <kbd>Delete</kbd> | Delete the selected blocks, the selected atom, the selected range or one character; at a block edge, see [by role](#enter-and-backspace-by-role) | `deleteBlocks`, `removeInlineBlock`, `deleteContentWithinSelection` or `deleteText` | Anywhere |
| <kbd>Mod</kbd> + <kbd>Z</kbd> | Undo | none (the [history](/docs/editor/history)) | Anywhere |
| <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>Z</kbd> | Redo | none | Anywhere |
| <kbd>Mod</kbd> + <kbd>Y</kbd> | Redo | none | Windows and Linux |

### Enter and Backspace by role

This table is the reference for what <kbd>Enter</kbd> and <kbd>Backspace</kbd> do at a block's edges. A block's role comes from its kind's record ([custom blocks](/docs/customization/blocks)); the bundled kinds are listed in [Blocks](/docs/concepts/blocks#bundled-kinds). A block can have more than one role: a toggle continues and is a container.

| Role | Kinds | <kbd>Enter</kbd> | <kbd>Backspace</kbd> at the start |
| --- | --- | --- | --- |
| Plain | paragraph, heading | At the end, a block of the parent's default child kind after it (`insertBlockAfter`); at the start, one before it (`insertBlockBefore`); in the middle, a split whose second half takes the default child kind (`splitBlock`). At the end of a block with text and children, a split that keeps the kind and takes the children. | A kind the menus offer (one with `presets`) other than the parent's default child, such as a heading, turns into that default, keeping its text and children (`setBlock`). Otherwise, a nested block that is its parent's last child moves out one level (`unNestBlock`), and any other merges into the block before it (`mergeBlockBackward`). |
| Continuing (`continues`) | bulleted and numbered items, to-do, toggle | A block of the same kind with its first preset's data (a new to-do is unchecked), after it at the end and before it at the start; a split in the middle keeps the kind. At the end of one with text and children, a split whose second half keeps the kind, with its first preset's data (an unchecked to-do), and takes the children. In an empty one without children, it ends the run: it moves out one level when its parent is a continuing kind too (`unNestBlock`), else it turns into the parent's default child in place (`setBlock`). | As plain: turns into the parent's default child first. |
| Container (`container`) | toggle, callout, quote | At the end of one with children, or of an open toggle, a first child of its default child kind (`addChildBlock`). At the end of a closed toggle, a new toggle after it; its children stay. In the middle of the header of one with children, or of an open toggle, the text after the caret becomes its first child, of its default child kind, and the children stay with the header (`splitBlock`, one step); in a closed toggle's, it goes to a new toggle after it and the children stay. Otherwise as its other role. | As plain. A closed toggle is one unit, as in Notion: <kbd>Backspace</kbd> at the start of the block after it merges that block into the toggle's header (its children take its place), and <kbd>Delete</kbd> at the end of the header merges the block after the toggle, never the hidden children. |
| Void (`void`) | divider, image | A void block's own text (an image caption) never splits. At the caption's end or start, a block after or before it. | Backspace at the start of the block after a void block selects it; a second <kbd>Backspace</kbd> deletes it. <kbd>Delete</kbd> at the end of the block before it does the same. A void block never merges. |
| Island (`island`) | code | Inside, a new line (a `codeLine`). | Lines merge inside the island only: the first line never joins the block before it, and with a single line, <kbd>Backspace</kbd> selects the whole island. With the code plugin, <kbd>Delete</kbd> at the end of the block before a code block removes that block when it is empty (the caret goes to the end of the text before it, or to the start of the code when nothing comes before) and does nothing otherwise, and <kbd>Backspace</kbd> in an empty block right after a code block removes that block and puts the caret at the end of the code. |

### Selection

| Keys                                          | Action                                                                                   | Where                         |
| --------------------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------- |
| <kbd>Mod</kbd> + <kbd>A</kbd>                   | Select the block's text; press again to select the block, then every block               | Anywhere                      |
| <kbd>↑</kbd>, <kbd>↓</kbd>                    | Select the previous or next block                                                        | One selected block            |
| <kbd>Shift</kbd> + <kbd>↑</kbd>, <kbd>Shift</kbd> + <kbd>↓</kbd> | Add the next block to the selection, or remove the last one when moving back | Block selection               |
| <kbd>Shift</kbd> + <kbd>↑</kbd>, <kbd>Shift</kbd> + <kbd>↓</kbd> | Extend the text selection one line; at the end of a block, <kbd>Shift</kbd> + <kbd>↓</kbd> selects a following void block | Text |
| <kbd>Escape</kbd>                             | Leave the block selection, caret at the end of the first selected block                  | Block selection               |

Arrow keys that walk a block selection do not enter an island (such as a code block) from outside, and they skip the hidden body of a closed toggle.

### Navigation

Every navigation key also has a <kbd>Shift</kbd> variant that extends the selection instead of moving the caret.

| Keys                                                   | Moves to                                   | Where              |
| ------------------------------------------------------ | ------------------------------------------ | ------------------ |
| <kbd>←</kbd>, <kbd>→</kbd>                             | The previous or next character, across blocks and atoms | Text  |
| <kbd>Alt</kbd> + <kbd>←</kbd>, <kbd>Alt</kbd> + <kbd>→</kbd> | The previous or next word                | macOS              |
| <kbd>Mod</kbd> + <kbd>←</kbd>, <kbd>Mod</kbd> + <kbd>→</kbd> | The previous or next word                | Windows and Linux  |
| <kbd>Home</kbd>, <kbd>End</kbd>                        | The start or end of the block              | Text               |
| <kbd>PageUp</kbd>, <kbd>PageDown</kbd>                 | The start or end of the document           | Text               |
| <kbd>Mod</kbd> + <kbd>↑</kbd>, <kbd>Mod</kbd> + <kbd>↓</kbd> | The start or end of the document         | Text               |
| <kbd>↑</kbd>, <kbd>↓</kbd>                             | The previous or next line (native)         | Text               |

Left, right and word keys follow the visual direction of right-to-left text, and they skip the body of a closed toggle.

### macOS text bindings

On macOS the Emacs-style <kbd>Ctrl</kbd> keys work as in native text fields:

| Keys                              | Action                                                      |
| --------------------------------- | ----------------------------------------------------------- |
| <kbd>Ctrl</kbd> + <kbd>A</kbd>, <kbd>Ctrl</kbd> + <kbd>E</kbd> | Start or end of the block              |
| <kbd>Ctrl</kbd> + <kbd>B</kbd>, <kbd>Ctrl</kbd> + <kbd>F</kbd> | Previous or next character             |
| <kbd>Ctrl</kbd> + <kbd>P</kbd>, <kbd>Ctrl</kbd> + <kbd>N</kbd> | Like <kbd>↑</kbd> and <kbd>↓</kbd>     |
| <kbd>Ctrl</kbd> + <kbd>H</kbd>, <kbd>Ctrl</kbd> + <kbd>D</kbd> | Delete backward or forward             |
| <kbd>Ctrl</kbd> + <kbd>K</kbd>      | Delete to the end of the block; at the end, join the next block |
| <kbd>Ctrl</kbd> + <kbd>O</kbd>      | Insert a line break, keeping the caret before it            |

### Plugin keys

Rich text, arrow move and block handles are on by default in `<Edytor>`; the others apply when you list their plugin.

| Keys                                               | Action                                           | Plugin, where                            |
| -------------------------------------------------- | ------------------------------------------------ | ---------------------------------------- |
| <kbd>Mod</kbd> + <kbd>B</kbd>, <kbd>I</kbd>, <kbd>U</kbd>, <kbd>E</kbd> | Bold, italic, underline, inline code | [Rich text](/docs/plugins/rich-text)     |
| <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd>, <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>X</kbd> | Strikethrough              | Rich text                                |
| <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>H</kbd>       | Red text color                                   | Rich text                                |
| <kbd>Mod</kbd> + <kbd>Enter</kbd>                    | Check or uncheck the to-do                       | Rich text, in a to-do                    |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>0</kbd> to <kbd>8</kbd> | Turn into text, heading 1 to 3, to-do, bulleted list, numbered list, toggle, code (a block with text or children keeps them, and the code block is inserted after it) | Rich text, when that kind's command exists |
| <kbd>Tab</kbd>                                     | Insert a tab at the caret, indent every line a selection touches, or accept a suggestion | [Code](/docs/plugins/code), in a code line |
| <kbd>Shift</kbd> + <kbd>Tab</kbd>                    | Remove one leading tab (or up to two spaces) from every line the selection touches | Code, in a code line                     |
| <kbd>Shift</kbd> + <kbd>Enter</kbd>                  | New code line                                    | Code, in a code line                     |
| <kbd>Mod</kbd> + <kbd>A</kbd>                        | Select the whole code block's text               | Code, in a non-empty code line           |
| <kbd>Escape</kbd>                                  | Dismiss a suggestion                             | Code, in a code line                     |
| <kbd>↑</kbd>, <kbd>↓</kbd>, <kbd>Enter</kbd>, <kbd>Escape</kbd> | Navigate, run, close                | [Slash menu](/docs/plugins/slash-menu), while open |
| <kbd>Mod</kbd> + <kbd>↑</kbd>, <kbd>Mod</kbd> + <kbd>↓</kbd> | Move the selected blocks up or down        | [Arrow move](/docs/plugins/arrow-move), block selection |
| <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>↑</kbd>, <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>↓</kbd> | Move the caret's block (or the selected blocks) up or down | Arrow move, text or block selection |
| <kbd>Mod</kbd> + <kbd>D</kbd>                        | Duplicate the selected block (the first one), or else the caret's block | [Block menu](/docs/plugins/block-menu)   |
| <kbd>Alt</kbd> + <kbd>↑</kbd> <kbd>↓</kbd> <kbd>→</kbd> <kbd>←</kbd> | Move the block up, down, in, out | [Block handles](/docs/plugins/block-handles), handle focused |

The <kbd>Mod</kbd> + <kbd>Alt</kbd> digits follow Notion's numbering and run the kind's `block.*` command, so <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>8</kbd> needs the code plugin. The block menu's own keys (arrows, <kbd>Enter</kbd>, <kbd>Escape</kbd>) belong to its search field, see [Block menu](/docs/plugins/block-menu#keys).

A plugin key only claims the key in the situation listed. Otherwise the key falls through to the next binding, for example <kbd>Mod</kbd> + <kbd>↓</kbd> to the document end when no block is selected, or <kbd>Mod</kbd> + <kbd>Enter</kbd> to the built-in new block outside a to-do. With the arrow move plugin (a default), <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>↑</kbd>/<kbd>↓</kbd> moves a movable block instead of extending the selection to the document edge.

## Adding hotkeys

A plugin declares bindings in `hotkeys`. The app can pass its own with the `hotKeys` prop of `<Edytor>`. Both take the same functions:

```ts
import type { Plugin } from 'edytor';

export const savePlugin: Plugin = () => ({
	hotkeys: {
		'mod+s': ({ edytor, prevent }) => {
			prevent(() => {
				localStorage.setItem('doc', JSON.stringify(edytor.value));
			});
		}
	}
});
```

A binding receives `{ edytor, prevent, event? }`:

- `prevent()` claims the key: later bindings do not run, and the browser's default action and propagation are stopped. `prevent(cb)` claims it and runs `cb`. Like every `prevent`, it throws, so code after it does not run.
- A binding that does not call `prevent` leaves the key to the next binding, and finally to the browser.
- `event` is the `KeyboardEvent`. It is absent when the key arrived only as an input event, as with some virtual keyboards on Android.

The `hotKeys` prop is read once, when the editor mounts.

### Chord syntax

A chord is lowercase modifiers and one key, joined with `+`:

- **Modifiers**: `mod`, `alt`, `ctrl`, `shift`, up to three. Their order does not matter: `shift+mod+k` and `mod+shift+k` are the same chord. Case does not matter either at runtime, but the types expect lowercase.
- **Keys**: the letters `a` to `z`, the digits `0` to `9`, the punctuation keys (`/`, `.`, `[`, `+`, …, as `KeyboardEvent.key` names them), `arrowup`, `arrowdown`, `arrowleft`, `arrowright`, `tab`, `enter`, `backspace`, `delete`, `space`, `escape`, `home`, `end`, `pageup`, `pagedown`, `insert`, `f1` to `f12`. `space` is the space bar.
- Other names (`cmd`, `meta`, `option`, `esc`, `return`, `del`, `up`…) never match a key press. They fail type-checking, and in development the editor logs a warning naming the chord.
- `mod` is <kbd>Cmd</kbd> on Apple platforms and <kbd>Ctrl</kbd> elsewhere. `ctrl` matches the <kbd>Ctrl</kbd> key only on Apple platforms; elsewhere <kbd>Ctrl</kbd> is always `mod`.
- On non-Latin keyboard layouts, a <kbd>Mod</kbd> chord also matches by physical key: <kbd>Mod</kbd> + <kbd>Б</kbd> on a Russian layout runs `mod+b`.
- <kbd>AltGr</kbd> combinations and dead keys produce text and never run a binding.
- A punctuation key is the character the key press types. A character typed with <kbd>Shift</kbd> needs `shift` in the chord: <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>/</kbd> is `mod+shift+?`, so `mod+?` and `shift+/` never match. On macOS, <kbd>Option</kbd> with a character key types a character (<kbd>Option</kbd> + <kbd>B</kbd> is `∫`), so `alt+b` never matches there; bind `mod+alt+b` instead. In development the editor logs a warning for these chords too.

A plugin's `hotkeys` and the `hotKeys` prop are type-checked against this syntax (`HotKeyCombination`).

<kbd>Enter</kbd>, <kbd>Shift</kbd> + <kbd>Enter</kbd>, <kbd>Backspace</kbd> and <kbd>Delete</kbd> reach bindings too, so a plugin can claim them, as the slash menu does with <kbd>Enter</kbd>. Each key press runs its bindings once.

## Precedence

For each chord, the bindings run in this order until one calls `prevent`:

1. the `hotKeys` prop of `<Edytor>`;
2. each plugin's `hotkeys`, in the order of the `plugins` array;
3. the built-in bindings.

A binding that is not a function, such as an optional callback left `undefined` (`'mod+e': onInlineCode`), is skipped: the chord's next binding runs, and in development the editor logs a warning naming the chord. TypeScript accepts `undefined` for these optional keys, so add an optional callback only when it is set: `...(onInlineCode && { 'mod+e': onInlineCode })`.

## Overriding and disabling defaults

To override a default, bind the same chord in the `hotKeys` prop or in a plugin, and call `prevent`:

```svelte
<script lang="ts">
	import { Edytor } from 'edytor';

	let saved = $state(0);
</script>

<Edytor
	hotKeys={{
		// Mod+Enter saves instead of starting a new block.
		'mod+enter': ({ prevent }) => prevent(() => saved++),
		// Tab never nests: claim it and do nothing.
		tab: ({ prevent }) => prevent(),
		// Mod+B does nothing in headings; elsewhere the rich text binding runs.
		'mod+b': ({ edytor, prevent }) => {
			if (edytor.selection.state.startBlock?.type === 'heading') prevent();
		}
	}}
/>
```

`prevent()` without a callback disables the key in the editor. It also prevents the browser's default, so a disabled <kbd>Tab</kbd> does not move focus out of the editor either. There is no way to remove a built-in binding and hand the key back to the browser.
