Skip to content

List Item

The ListItem node represents an individual <li> element inside a BulletList or OrderedList. It handles Enter key splitting (creating new items or exiting the list), and automatically includes the ListKeymap extension for Tab/Shift-Tab indentation.

You don’t need to add ListItem manually. It is automatically included when you add BulletList or OrderedList via their addExtensions(). If you are using StarterKit, ListItem is already included.

import { Editor, Document, Text, Paragraph, BulletList, OrderedList } from '@domternal/core';
const editor = new Editor({
element: document.getElementById('editor')!,
extensions: [Document, Text, Paragraph, BulletList, OrderedList],
content: '<ul><li><p>First item</p></li><li><p>Second item</p></li></ul>',
});
PropertyValue
ProseMirror namelistItem
TypeNode
GroupNone (used as content of list nodes)
Contentblock+ (one or more block nodes)
DefiningYes
HTML tag<li>

The content expression block+ means a list item can contain paragraphs, headings, code blocks, nested lists, and other block-level nodes.

The defining property means that when you select a list item and paste content over it, the replacement keeps the list item wrapper.

OptionTypeDefaultDescription
HTMLAttributesRecord<string, unknown>{}HTML attributes added to the <li> element
import { ListItem } from '@domternal/core';
const CustomListItem = ListItem.configure({
HTMLAttributes: { class: 'my-list-item' },
});
ShortcutBehavior
EnterSplit list item at cursor position
Enter (empty item)Lift content out of the list (exit the list)
TabIndent list item (sink into nested list)
Shift-TabOutdent list item (lift out of nested list)
BackspaceLift list item when at the start of an empty item

The Enter key has context-dependent behavior:

  1. Non-empty item, cursor in middle: Splits the list item at the cursor, creating a new item with the text after the cursor.
  2. Non-empty item, cursor at end: Creates a new empty list item below.
  3. Empty item: Lifts the content out of the list. If the item is the last one, pressing Enter on an empty item exits the list and creates a paragraph below.
  4. Empty item inside a nested list within a task item: Escapes to the parent task level by creating a new task item, rather than leaving a bare paragraph.

Tab and Shift-Tab are provided by the ListKeymap extension (automatically included). Tab sinks the current item into a nested list under the previous sibling, while Shift-Tab lifts it out one level.

ListItem automatically includes the ListKeymap extension via addExtensions():

ExtensionDescription
ListKeymapTab/Shift-Tab indentation and Backspace handling

A list item always wraps its content in block nodes:

{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "List item text" }
]
}
]
}

A list item with a nested list:

{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Parent item" }
]
},
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Nested item" }
]
}
]
}
]
}
]
}

@domternal/core - ListItem.ts