Skip to content

Bullet List

The BulletList node provides a block-level unordered list container (<ul>). It includes markdown-style input rules (-, *, or + followed by space), a keyboard shortcut (Mod-Shift-8), and automatic inclusion of the ListItem node and ListKeymap extension for Tab/Shift-Tab indentation.

Choose BulletList when you need:

  • Markdown -, *, or + shortcuts to convert text into a bullet list
  • Nestable list items with Tab to indent and Shift+Tab to outdent
  • Smart join when the user presses Backspace at the start of the first item
  • Cross-list merging when typing a bullet between two existing lists

Skip it if:

  • You need numbered items (use OrderedList standalone)
  • You need checkbox to-do behavior (use TaskList instead)
  • You need flat lists with no nesting (drop ListKeymap and ListIndent)

Type -, *, or + followed by space at the start of a line to create a bullet list. Use Tab to indent and Shift-Tab to outdent.

Click to try it out

BulletList is included in StarterKit. If you are building a custom setup without StarterKit, add it manually:

import { Document, Text, Paragraph, BulletList } from '@domternal/core';
import { DomternalEditor } from '@domternal/vanilla';
const dm = new DomternalEditor(document.getElementById('editor')!, {
extensions: [Document, Text, Paragraph, BulletList],
content: '<ul><li><p>First item</p></li><li><p>Second item</p></li></ul>',
});
PropertyValue
ProseMirror namebulletList
TypeNode
Groupblock list
ContentlistItem+ (one or more list items)
HTML tag<ul>

The list group allows ProseMirror to identify this node as a list container, which is used by list-related commands like toggleList.

OptionTypeDefaultDescription
HTMLAttributesRecord<string, unknown>{}HTML attributes added to the <ul> element
itemTypeNamestring'listItem'Name of the list item node type used by toggleList
import { BulletList } from '@domternal/core';
const CustomBulletList = BulletList.configure({
HTMLAttributes: { class: 'my-list' },
});
CommandDescription
toggleBulletList()Toggle the whole list between bullet list and paragraph
turnIntoBulletList()Convert only the cursor’s item to a bullet list (Notion per-item turn-into)
// Toggle bullet list on/off
editor.commands.toggleBulletList();
// With chaining
editor.chain().focus().toggleBulletList().run();

toggleBulletList wraps the selected blocks in a <ul> with <li> items. If the selection is already inside a bullet list, it unwraps it back to paragraphs. If the selection is inside a different list type (ordered list, task list), it converts it to a bullet list.

Whole-list toggle vs per-item turn-into (v0.9.0)

Section titled “Whole-list toggle vs per-item turn-into (v0.9.0)”

The two commands differ in scope when the cursor sits inside a list of another kind:

  • toggleBulletList() converts the whole list. Used by the toolbar button and the Mod-Shift-8 shortcut, preserving classic-editor semantics.
  • turnIntoBulletList() converts only the cursor’s item and splits the run, leaving its siblings untouched (Notion-style). This is what the slash command’s “Bulleted list” entry and the block menu’s “Turn into” submenu use. Internally it calls toggleList(name, itemTypeName, undefined, { perItem: true }).
ShortcutCommand
Mod-Shift-8toggleBulletList()
EnterSplit list item at cursor, or lift out of list if empty
TabIndent list item (sink)
Shift-TabOutdent list item (lift)
BackspaceLift list item when at the start of an empty item

The Enter, Tab, Shift-Tab, and Backspace shortcuts are provided by the ListItem node and ListKeymap extension, which are automatically included when you add BulletList.

The list-item children-zone is now strictly paragraph block* (see List Item migration). Several Notion-style behaviors follow:

  • Backspace on an empty paragraph immediately after a list: Deletes the paragraph and places the caret at the end of the last item (instead of wrapping it back as a list item).
  • Backspace on an empty paragraph between two same-type lists: Joins the two lists back into one.
  • toggleList in a children-zone paragraph: Wraps that paragraph in a FRESH list (does not convert the ancestor list type).
  • Tab on a top-level paragraph immediately after a list: With the opt-in ListIndent extension (off by default), indents the paragraph INTO the previous list as a nested child. Without it, Tab moves focus to the next field as usual.
InputResult
- + spaceBullet list item
* + spaceBullet list item
+ + spaceBullet list item

Type one of these characters at the start of a new line, then press space. The line wraps in a bullet list. These input rules only fire outside of existing lists - typing - + space inside a list item inserts the characters as plain text. To nest lists, use Tab to indent a list item.

The rule joins adjacent bullet lists on both sides: creating a bullet on a line that sits between two bullet lists merges all of them into a single list, rather than leaving the following list as a separate sibling.

BulletList registers a button in the toolbar with the name bulletList in group lists at priority 200.

ItemCommandIconShortcut
Bullet ListtoggleBulletListlistBulletsMod-Shift-8

BulletList automatically includes these extensions via addExtensions():

ExtensionDescription
ListItemThe <li> node used inside the list
ListKeymapTab/Shift-Tab indentation and Backspace handling (included by ListItem)

This means adding BulletList to your extensions array is sufficient. You don’t need to add ListItem or ListKeymap separately.

{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "First item" }
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Second item" }
]
}
]
}
]
}

A nested bullet list:

{
"type": "bulletList",
"content": [
{
"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 - BulletList.ts

  • Ordered List - numbered list with 1. markdown shortcut
  • Task List - checkbox to-do list container
  • List Item - list item used by bullet/ordered/task lists
  • List Keymap - Backspace/Enter behavior for list items
  • List Indent - Tab/Shift-Tab to sink/lift list items