List turns supported blocks into flat list items. Kind, marker style, list identity, numbering starts, task state, and indentation are separate fields.
import { ListKit } from '@/components/editor/list';
import { createEditor } from 'platejs/react';
const editor = createEditor({
plugins: [...ListKit],
});import { ListKit } from '@/components/editor/list';
import { createEditor } from 'platejs/react';
const editor = createEditor({
plugins: [...ListKit],
});const value = [
{
type: 'paragraph',
listType: 'numbered',
listId: 'steps',
listStart: 4,
indent: 1,
children: [{ text: 'Four' }],
},
{
type: 'paragraph',
children: [{ text: 'A note between the steps.' }],
},
{
type: 'paragraph',
listType: 'numbered',
listId: 'steps',
indent: 1,
children: [{ text: 'Five' }],
},
{
type: 'paragraph',
listType: 'task',
listId: 'todo',
checked: false,
indent: 1,
children: [{ text: 'Ship it' }],
},
];const value = [
{
type: 'paragraph',
listType: 'numbered',
listId: 'steps',
listStart: 4,
indent: 1,
children: [{ text: 'Four' }],
},
{
type: 'paragraph',
children: [{ text: 'A note between the steps.' }],
},
{
type: 'paragraph',
listType: 'numbered',
listId: 'steps',
indent: 1,
children: [{ text: 'Five' }],
},
The third block does not store 5. Plate derives it with editor.read.list.ordinal(at), where at is the item's path, node or key; a static renderer that already knows the path passes it.
Plate numbers each list on its own, in document order. Blocks of other lists and plain blocks between its items do not end it. A numbered item shows:
Any item of the list closes its deeper levels, so a nested list restarts under each parent. A level's restartAfter rule in listLevels changes that. With 0 the level never restarts, and with n it restarts only after an item at level n or shallower. Each numbered item reads the rule from its own listLevels, and a value at or past its own level acts as the default.
Items without listId form their root's default list. Give each list its own id in values that hold more than one list. Commands and typed list markers give a new item an id. It joins a compatible item at its depth before it, otherwise the item it nests under, otherwise a compatible item after it. When none is within 64 blocks, it starts a new list. An item any other edit inserts without an id stays in its root's default list.
listStart is latent author intent. Plate keeps it while the item continues its list and applies it when later edits make that item open its level. listRestart forces the number even inside a list.
Both fields are signed safe integers, so zero and negative ordered-list starts remain valid.
import { ListStyle, ListType } from 'platejs';
import { ListPlugin } from 'platejs/react';
const list = editor.plugin(ListPlugin);
list.update.toggle({ type: ListType.Bulleted });
list.update.toggle({
type: ListType.Bulleted,
listStyle: ListStyle.Square,
});
list.update.toggle({
type: ListType.Numbered,
listStart: 4,
});
list.update.toggle({ type: ListType.Task });
list.update.restart();
list.update.restart({ start: 4 });
list.update.continue();
list.update.outdent();
list.update.clear();
list.update.insert({ type: ListType.Numbered }, { replaceEmpty: true, select: true });import { ListStyle, ListType } from 'platejs';
import { ListPlugin } from 'platejs/react';
const list = editor.plugin(ListPlugin);
list.update.toggle({ type: ListType.Bulleted });
list.update.toggle({
type: ListType.Bulleted,
listStyle: ListStyle.Square,
});
list.update.toggle({
type: ListType.Numbered,
listStart: 4,
});
list.update.toggle({ type: ListType.Task });
list.update.restart();
list.update.restart({ start: 4 });
list.update.continue();
restart forces the selected numbered item's number, 1 by default. continue
removes that restart. On the first item of a list, it moves the whole list onto
the nearest earlier numbered list at its depth, so the numbers continue it.
clear removes list fields and indentation from the selected list items without
expanding to their unselected siblings. It preserves each block's type and text.
Pass at to target a location or node selection. insert creates a paragraph
list item after the current block; replaceEmpty replaces an empty source in
the same history step. An explicit at keeps node-insertion placement, while
after identifies the block to insert beside.
Native paste and the one-shot editor.update.slice.replace and
editor.update.fragment.replace methods apply List's depth, numbering and
empty-target rules. Callback transaction replacements and detached fitting
bypass this command policy.
At a text caret, an admitted whole-item paste into an empty paragraph or heading keeps the copied text properties allowed by the schema. Permitted target properties, such as an id or alignment, survive unless the copied item supplies them. Object and void items do not inherit those target properties. The empty target's explicit start and restart take precedence over copied values. The replacement gets its own runtime node key.
Under maxLength, a paste lands what fits. When the limit cuts the first copied
item, the items left land at the caret's depth. A quote-wrapped list pastes
nothing once its text no longer fits.
HTML ul and ol tags map to listType. Explicit CSS list-style-type maps to listStyle. Plate HTML names each item's list with data-list-id and its level rules with data-list-levels. Each paste or parse gives every source list a fresh id, so a pasted list never joins an unrelated list that shares its id. The first top-level pasted list joins the target list when their kind and heading role match. Its root items take the target marker style. Copied siblings land at the target item's depth, and children keep their depth relative to a copied parent. A list-led paste of multiple blocks or a closed slice keeps the first item's type in an empty paragraph or heading, including inside a quote, table cell or column when the schema allows it. An empty line below a compatible list continues that list. The empty target's own start and restart take precedence over copied values. A list that lives only inside a pasted table keeps its own id. In other HTML, each ul or ol is one list. An ol whose start continues the previous ol at its depth joins it, and the first nested list under an item joins that item's list. Word HTML keeps Word's list identity. A list drawn inside its block, such as <h2><ol data-list-id="…"><li>…</li></ol></h2> from the static kit, reads back as that block with its list fields, and an item's own data-list-style wins over its list's marker.
A custom object whose body lives in a separate content root needs a whole-object copy to preserve that body. Copying only its caption can omit the body; that partial copy does not guarantee the object type or list numbering.
MDAST list.ordered maps to listType. Each parsed Markdown list gets a new
listId, and a start other than 1 parses as listStart. The first nested list
under an item joins that item's list, and a later nested list under the same
item starts its own. When serializing, Plate starts a new Markdown list where
the list changes, where items switch between ordered and unordered, or where
listStyle changes, so task and bullet items of one list stay one Markdown list.
It writes the visible number of each ordered list's first item as its
list.start, or 1 when the list's numbers would go negative or pass the nine
digits CommonMark reads. Markdown cannot carry a forced listRestart. It
also cannot carry list membership that reads back differently, such as a list
that continues past another block; Plate checks
membership only for items with a listId. Serialize reports each loss as a
markdown-property-omitted diagnostic. Markdown parses an item's later paragraphs as blocks without listType, one level deeper than the item, and such a block serializes inside the item before it. Task checkboxes map to task listType plus checked. Inside a GFM table cell, list paragraphs are written as inline
<ul>, <ol start>, and <li> HTML, which keeps any start, and read back as lists; see
Markdown.
An item's later blocks, such as a second paragraph, a code block or an image,
parse in both formats as blocks one level under the item, with no listType:
{ type: 'codeBlock', indent: 2 } after an item at indent 1. Plate HTML writes
them after the list. When HTML nests them inside the li, an item's
margin-left counts as its level. A style that the li or a wrapper sets,
such as bold, does not reach the content that moves out of that element, and
the parse warns once with an html-unsupported-content diagnostic that names
the first such style. That includes white-space, so a line break that
pre-wrap kept becomes a space. An image inside the item's paragraph stays
where the HTML runtime puts it, after the item's text and outside the list,
and the text after it becomes another list item.
When the editor cannot place a list property on a block, such as indent on a
code block that IndentPlugin does not target, the block keeps its type and
drops that property. HTML reports it as html-unsupported-content and Markdown
as markdown-unsupported-node, a warning under every lossPolicy. A Markdown
item whose first block the list cannot hold, such as a heading when ListPlugin
targets only paragraphs, reads as a list paragraph. A first block that is not
a line of text, such as a rule or a quote, keeps its type and leaves the list instead.
The parse reports either change, so lossPolicy: 'reject' refuses it.
| API | Purpose |
|---|---|
| ListType | Semantic bulleted, numbered, and task constants. |
| ListStyle | CSS marker constants. |
| update.toggle(options) | Set or remove list semantics. |
| update.restart(options?) | Force the selected numbered item's number; start defaults to 1. |
| update.continue(options?) | Remove the selected item's restart, or join the list to the previous one from its first item. |
| update.outdent(options) | Decrease indentation and clear list fields at the outer level. |
| update.clear(options?) | Remove list semantics from the exact selected list items. |
| update.insert(input, options?) | Insert a list item with owned construction and placement. |
| read.ordinal(at) | Derive a numbered item's display ordinal, or undefined when at is not a numbered item in the editor's root. |