Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Stream
    • Copilot
  • Comments
  • Discussion
  • Suggestions
    • Basic Blocks
      • Blockquote
      • Heading
      • Horizontal Rule
    • Callout
    • Code Block
    • Column
    • Date
    • Equation
    • Link
    • Media
    • MentionElement
    • Table
    • Table of Contents
    • Footnote
    • Details
  • Marks
    • Bold
    • Italic
    • Underline
    • Code
    • Highlight
    • Keyboard Input
    • Strikethrough
    • Subscript
    • Superscript
      • Font
      • Line Height
      • Text Align
    • Indent
    • List
      • Exit Break
      • Single Block
      • Trailing Block
    • Autoformat
    • Block Menu
    • Block Placeholder
    • Combobox
      • Emoji
      • MentionElement
      • Slash Command
    • Drag & Drop
    • Navigation Feedback
    • Linting
    • Tabbable
    • Toolbar
    • Yjs
    • Multi SelectEditor
    • DOCX
    • HTML
    • Markdown

List

PreviousNext

Flat semantic list items with derived numbering.

List Toolbar Button

List turns supported blocks into flat list items. Kind, marker style, list identity, numbering starts, task state, and indentation are separate fields.

Loading…
IndentExit Break

On This Page

FeaturesKit usageValueNumberingCommandsPasting listsHTML and MarkdownAPI Reference
Build your editor
Production-ready AI template and reusable components.
Get all-access

Features

  • listType is bulleted, numbered, or task.
  • listStyle optionally selects a CSS marker.
  • listId names the list an item belongs to. A list counts on past other blocks, and two lists can interleave.
  • listLevels holds a list's per-level start and restart rules, as Word stores them.
  • listStart applies when the item opens its level; listRestart forces the item's number.
  • checked belongs to task items.
  • Display ordinals are derived without rewriting following nodes.
  • Restart numbering and Continue numbering commands.
  • Markdown rules cover bullets, numbers, and tasks.
Report an issue

Kit usage

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],
});

Value

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.

Numbering

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:

  • its listRestart, when set;
  • otherwise one more than the list's previous item at its level, while that level is open;
  • otherwise its listStart, then its level's listLevels start, then 1.

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.

Commands

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.

Pasting lists

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 and Markdown

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 Reference

APIPurpose
ListTypeSemantic bulleted, numbered, and task constants.
ListStyleCSS 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.
{
type: 'paragraph',
listType: 'task',
listId: 'todo',
checked: false,
indent: 1,
children: [{ text: 'Ship it' }],
},
];
list.update.outdent();
list.update.clear();
list.update.insert({ type: ListType.Numbered }, { replaceEmpty: true, select: true });