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 { Document, Text, Paragraph, TaskList } from '@domternal/core';import { DomternalEditor } from '@domternal/vanilla';
const dm = new DomternalEditor(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> `,});import { Component, signal } from '@angular/core';import { DomternalEditorComponent } from '@domternal/angular';import { Editor, Document, Text, Paragraph, TaskList } from '@domternal/core';
@Component({ selector: 'app-editor', imports: [DomternalEditorComponent], template: ` <domternal-editor [extensions]="extensions" [content]="content" (editorCreated)="editor.set($event)" /> `,})export class EditorComponent { editor = signal<Editor | null>(null); extensions = [Document, Text, Paragraph, TaskList]; content = '<ul data-type="taskList"><li data-type="taskItem" data-checked="false"><p>A task</p></li></ul>';}import { Domternal } from '@domternal/react';import { Document, Text, Paragraph, TaskList } from '@domternal/core';
const 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>`;
export default function Editor() { return ( <Domternal extensions={[Document, Text, Paragraph, TaskList]} content={content} > <Domternal.Content /> </Domternal> );}<script setup lang="ts">import { Domternal } from '@domternal/vue';import { Document, Text, Paragraph, TaskList } from '@domternal/core';
const extensions = [Document, Text, Paragraph, TaskList];
const 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>`;</script>
<template> <Domternal :extensions="extensions" :content="content"> <Domternal.Content /> </Domternal></template>TaskItem is auto-included by TaskList. Use the toggleTask command to toggle the checked state:
// Toggle the current task's checked stateeditor.commands.toggleTask();
// Check if a task item is activeeditor.isActive('taskItem'); // true or false
// TaskItem handles Enter splitting, Tab/Shift-Tab// indentation, and Mod-Enter toggle automaticallySchema
Section titled “Schema”| Property | Value |
|---|---|
| ProseMirror name | taskItem |
| Type | Node |
| Group | None (used as content of TaskList) |
| Content | paragraph block* (strict: paragraph label + optional children-zone) |
| Defining | Yes |
| HTML tag | <li data-type="taskItem"> |
The first child paragraph is the label - aligned with the checkbox baseline. Heading-first content would break checkbox visual alignment, hence the strict schema.
The rendered HTML structure is:
<li data-type="taskItem" data-checked="false"> <label contenteditable="false"> <input type="checkbox"> </label> <div> <p><!-- label paragraph (required first child) --></p> <!-- additional children-zone blocks (optional) --> </div></li>The <label> with contenteditable="false" prevents the cursor from entering the checkbox area. The <div> wrapper holds the editable content. In v0.7+ the checkbox click toggles the checked attribute via the NodeView (no longer requires Mod-Enter).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
HTMLAttributes | Record<string, unknown> | {} | HTML attributes added to the <li> element |
nested | boolean | true | Whether nested lists are allowed inside task items |
Custom HTML attributes
Section titled “Custom HTML attributes”import { TaskItem } from '@domternal/core';
const CustomTaskItem = TaskItem.configure({ HTMLAttributes: { class: 'my-task-item' },});Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
checked | boolean | false | Whether 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.
Commands
Section titled “Commands”| Command | Description |
|---|---|
toggleTask() | Toggle the checked state of the current task item |
// Toggle the current task's checked stateeditor.commands.toggleTask();
// With chainingeditor.chain().focus().toggleTask().run();toggleTask finds the nearest taskItem ancestor of the cursor and flips its checked attribute.
Keyboard shortcuts
Section titled “Keyboard shortcuts”| Shortcut | Cursor position | Behavior |
|---|---|---|
Enter | In label, non-empty | Split task item at the cursor; creates a new UNCHECKED item below |
Enter | In label, empty | Lift content out of the task list (exit) |
Enter | In children-zone, non-empty | Split in place (both halves stay in same item) |
Enter | In children-zone, empty paragraph | Insert empty paragraph as sibling INSIDE the same task item |
Enter | On a CHECKED task (label) | Spawns an UNCHECKED sibling (new-work assumption) |
Backspace | In label at offset 0 | Lift task item (v0.6.0 behavior preserved) |
Backspace | In empty children-zone paragraph | Lift as top-level paragraph below the list |
Tab | In any task-item slot | Sink into nested list |
Shift-Tab | In any task-item slot | Lift out one level |
| Click checkbox | - | Toggle checked attribute via NodeView (NEW in v0.7.0) |
Mod-Enter | - | Toggle checked state via command |
Enter behavior (v0.7.0)
Section titled “Enter behavior (v0.7.0)”The Enter behavior depends on whether the cursor is in the label paragraph (first child) or the children-zone (subsequent blocks):
In the label:
- Non-empty: Splits the task item, creating a new UNCHECKED item below (
splitListItemwithchecked: false). - Empty: Lifts the content out of the task list. If the item is the last one, Enter on the empty label exits the list and creates a paragraph below.
- Enter on a CHECKED task’s label (any position): The new sibling item is always UNCHECKED. The
checkedattribute is intentionally not forwarded (keepOnSplit: false) since the next task is new work. - Empty 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.
In the children-zone:
- Empty paragraph: Inserts a new empty paragraph as a sibling INSIDE the same task item.
- Non-empty paragraph: Splits in place via
splitBlock. Both halves stay in the same task item.
Checkbox click toggle (v0.7.0)
Section titled “Checkbox click toggle (v0.7.0)”The checkbox click is now routed through a NodeView so users can toggle without using Mod-Enter or the toolbar. The NodeView’s stopEvent guard prevents PM from treating the click as a selection.
Strikethrough on checked tasks is scoped to the label paragraph only - nested children-zone content stays unstruck (theme update in v0.7.0).
Tab/Shift-Tab
Section titled “Tab/Shift-Tab”Unlike ListItem which relies on ListKeymap for Tab/Shift-Tab, TaskItem handles these shortcuts directly using ProseMirror’s sinkListItem and liftListItem commands.
JSON representation
Section titled “JSON representation”{ "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" } ] } ]}Source
Section titled “Source”@domternal/core - TaskItem.ts