Skip to content

List Keymap

ListKeymap provides keyboard shortcuts for manipulating list items: Tab to indent (sink), Shift-Tab to outdent (lift), and Backspace at the start of a list item to lift it out of the list. It works with bullet lists and ordered lists but does not interfere with task lists, which use their own item type.

Included in StarterKit by default.

ListKeymap is included in StarterKit, so it works out of the box. To use it standalone:

import {
Editor, Document, Paragraph, Text,
BulletList, OrderedList, ListItem, ListKeymap,
} from '@domternal/core';
import '@domternal/theme';
const editor = new Editor({
element: document.getElementById('editor')!,
extensions: [Document, Paragraph, Text, BulletList, OrderedList, ListItem, ListKeymap],
content: '<ul><li>First item</li><li>Second item (press Tab to indent)</li></ul>',
});

To configure ListKeymap in StarterKit:

StarterKit.configure({
listKeymap: { listItem: 'listItem' },
})

To disable ListKeymap in StarterKit:

StarterKit.configure({ listKeymap: false })
OptionTypeDefaultDescription
listItemstring'listItem'Name of the list item node type that these shortcuts apply to
import { ListKeymap } from '@domternal/core';
const editor = new Editor({
extensions: [
ListKeymap.configure({
listItem: 'listItem',
}),
],
});

ListKeymap does not register any commands. It uses ProseMirror’s sinkListItem and liftListItem commands from prosemirror-schema-list directly in the keyboard shortcut handlers.

KeyActionDescription
TabSink (indent)Nests the current list item inside the previous sibling, creating a sub-list
Shift-TabLift (outdent)Moves the current list item one level up in the list hierarchy
BackspaceLift at startWhen the cursor is at the very start of a list item with an empty selection, lifts the item out of the list

Pressing Tab inside a list item indents it one level, wrapping it in a new sub-list under the previous sibling item. This uses ProseMirror’s sinkListItem command.

Before: After Tab on "Second":
- First item - First item
- Second item - Second item
- Third item - Third item

If there is no previous sibling (the item is first in the list), Tab does nothing.

Pressing Shift-Tab moves the list item one level up. This uses ProseMirror’s liftListItem command.

Before: After Shift-Tab on "Nested":
- First item - First item
- Nested item - Nested item

If the item is already at the top level, Shift-Tab lifts it out of the list entirely, converting it to a paragraph.

Pressing Backspace when the cursor is at the very start of a list item (empty selection, parentOffset === 0) lifts the item out of its parent list. This only triggers when:

  1. The selection is empty (collapsed cursor)
  2. The cursor is at position 0 within the parent text block
  3. The cursor is at the start of the list item’s first child (within 1 position of the list item’s start)

ListKeymap does not register any input rules.

ListKeymap does not register any toolbar items.

ListKeymap includes a guard (getListItemContext) that prevents its shortcuts from interfering with other list-like structures such as task lists. Before executing any shortcut, the function walks up the document tree from the cursor position:

  1. If it finds the target listItem type first, the shortcut runs
  2. If it encounters a different defining block node (like taskItem) inside a list parent first, the shortcut returns false and does nothing

This ensures that Tab/Shift-Tab inside a task list is handled by the TaskItem extension, not by ListKeymap.

The sinkListItem and liftListItem commands come from @domternal/pm/schema-list (ProseMirror’s prosemirror-schema-list package). They handle the structural tree transformations needed to nest and unnest list items while preserving content and maintaining valid document structure.

import { ListKeymap } from '@domternal/core';
import type { ListKeymapOptions } from '@domternal/core';
ExportTypeDescription
ListKeymapExtensionThe list keymap extension
ListKeymapOptionsTypeScript typeOptions for ListKeymap.configure()

@domternal/core - ListKeymap.ts