Skip to content

Task Item

The TaskItem node represents an individual checkbox list item (<li data-type="taskItem">) inside a TaskList. It renders a checkbox and a content area, tracks a checked attribute, and provides keyboard shortcuts for toggling, splitting, and indenting.

You don’t need to add TaskItem manually. It is automatically included when you add TaskList via its addExtensions(). If you are using StarterKit, TaskItem is already included.

import { Editor, Document, Text, Paragraph, TaskList } from '@domternal/core';
const editor = new Editor({
element: document.getElementById('editor')!,
extensions: [Document, Text, Paragraph, TaskList],
content: `
<ul data-type="taskList">
<li data-type="taskItem" data-checked="false"><p>Unchecked task</p></li>
<li data-type="taskItem" data-checked="true"><p>Checked task</p></li>
</ul>
`,
});
PropertyValue
ProseMirror nametaskItem
TypeNode
GroupNone (used as content of TaskList)
Contentblock+ (one or more block nodes)
DefiningYes
HTML tag<li data-type="taskItem">

The rendered HTML structure is:

<li data-type="taskItem" data-checked="false">
<label contenteditable="false">
<input type="checkbox">
</label>
<div>
<!-- content goes here -->
</div>
</li>

The <label> with contenteditable="false" prevents the cursor from entering the checkbox area. The <div> wrapper holds the editable content.

OptionTypeDefaultDescription
HTMLAttributesRecord<string, unknown>{}HTML attributes added to the <li> element
nestedbooleantrueWhether nested lists are allowed inside task items
import { TaskItem } from '@domternal/core';
const CustomTaskItem = TaskItem.configure({
HTMLAttributes: { class: 'my-task-item' },
});
AttributeTypeDefaultDescription
checkedbooleanfalseWhether the task is checked/completed

The checked attribute is stored as data-checked="true" or data-checked="false" on the <li> element, and also controls the checked attribute on the inner <input type="checkbox">.

When splitting a task item with Enter, the checked attribute is not carried over to the new item (keepOnSplit: false). New items always start unchecked.

CommandDescription
toggleTask()Toggle the checked state of the current task item
// Toggle the current task's checked state
editor.commands.toggleTask();
// With chaining
editor.chain().focus().toggleTask().run();

toggleTask finds the nearest taskItem ancestor of the cursor and flips its checked attribute.

ShortcutBehavior
EnterSplit task item at cursor position
Enter (empty item)Lift content out of the task list (exit the list)
TabIndent task item (sink into nested list)
Shift-TabOutdent task item (lift out of nested list)
Mod-EnterToggle checked state

The Enter key has context-dependent behavior:

  1. Non-empty item, cursor in middle: Splits the task item at the cursor, creating a new unchecked task item with the text after the cursor.
  2. Non-empty item, cursor at end: Creates a new empty unchecked task item below.
  3. Empty item: Lifts the content out of the task 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 regular list item: Escapes to the parent list level by creating a new list item at the correct depth.

Unlike ListItem which relies on ListKeymap for Tab/Shift-Tab, TaskItem handles these shortcuts directly using ProseMirror’s sinkListItem and liftListItem commands.

{
"type": "taskItem",
"attrs": { "checked": false },
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Unchecked task" }
]
}
]
}

A checked task item:

{
"type": "taskItem",
"attrs": { "checked": true },
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Completed task" }
]
}
]
}

@domternal/core - TaskItem.ts