Features
- Flat list model: Any supported block can become a list item without a nested list-node tree.
- Bulleted, numbered, and todo styles: Use CSS list styles or
KEYS.listTodo. - Automatic indentation support:
ListPlugininstalls its required Indent dependency. - Targeted commands: Toggle, indent, or outdent the selection, a path, a point, or a range.
- Input rules: Register the shipped Markdown rules for
-,*,1., and task-list syntax.
Kit Usage
ListKit configures list rendering, input rules, and the block types that support lists.
'use client';
import {
BulletedListRules,
isOrderedList,
OrderedListRules,
TaskListRules,
} from '@platejs/list';
import { ListPlugin } from '@platejs/list/react';
import { KEYS } from 'platejs';
import { IndentKit } from '@/components/editor/plugins/indent-kit';
import { BlockList } from '@/components/ui/block-list';
export const ListKit = [
...IndentKit,
ListPlugin.configure({
inputRules: [
BulletedListRules.markdown({ variant: '-' }),
BulletedListRules.markdown({ variant: '*' }),
OrderedListRules.markdown({ variant: '.' }),
OrderedListRules.markdown({ variant: ')' }),
TaskListRules.markdown({ checked: false }),
TaskListRules.markdown({ checked: true }),
],
inject: {
nodeProps: {
nodeKey: KEYS.listType,
query: ({ nodeProps }) => {
const element = nodeProps.element;
return !!element?.listStyleType && !isOrderedList(element);
},
transformProps: ({ props }) => ({
...props,
role: 'listitem',
style: {
...props.style,
display: 'list-item',
},
}),
},
targetPlugins: [
...KEYS.heading,
KEYS.p,
KEYS.blockquote,
KEYS.codeBlock,
KEYS.toggle,
KEYS.img,
],
},
render: {
belowNodes: BlockList,
},
}),
];'use client';
import {
BulletedListRules,
isOrderedList,
OrderedListRules,
TaskListRules,
} from '@platejs/list';
import { ListPlugin } from '@platejs/list/react';
import { KEYS } from 'platejs';
import { IndentKit } from '@/components/editor/plugins/indent-kit';
import { BlockList } from '@/components/ui/block-list';
export const ListKit = [
...IndentKit,
ListPlugin.configure({
inputRules: [
BulletedListRules.markdown({ variant: '-' }),
BulletedListRules.markdown({ variant: '*' }),
OrderedListRules.markdown({ variant: '.' }),
OrderedListRules.markdown({ variant: ')' }),
TaskListRules.markdown({ checked: false }),
TaskListRules.markdown({ checked: true }),
],
inject: {
nodeProps: {
nodeKey: KEYS.listType,
query: ({ nodeProps }) => {
const element = nodeProps.element;
return !!element?.listStyleType && !isOrderedList(element);
},
transformProps: ({ props }) => ({
...props,
role: 'listitem',
style: {
...props.style,
display: 'list-item',
},
}),
},
targetPlugins: [
...KEYS.heading,
KEYS.p,
KEYS.blockquote,
KEYS.codeBlock,
KEYS.toggle,
KEYS.img,
],
},
render: {
belowNodes: BlockList,
},
}),
];import { createPlateEditor } from 'platejs/react';
import { ListKit } from '@/components/editor/plugins/list-kit';
const editor = createPlateEditor({
plugins: [...ListKit],
});import { createPlateEditor } from 'platejs/react';
import { ListKit } from '@/components/editor/plugins/list-kit';
const editor = createPlateEditor({
plugins: [...ListKit],
});Add the List Toolbar Button for bulleted, numbered, and todo controls.
Manual Usage
Installation
pnpm add @platejs/listpnpm add @platejs/listAdd the Plugin
ListPlugin includes the default Indent configuration.
import { ListPlugin } from '@platejs/list/react';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [ListPlugin],
});import { ListPlugin } from '@platejs/list/react';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [ListPlugin],
});Configure List Rendering and Input Rules
import {
BulletedListRules,
OrderedListRules,
TaskListRules,
} from '@platejs/list';
import { ListPlugin } from '@platejs/list/react';
import { KEYS } from 'platejs';
import { BlockList } from '@/components/ui/block-list';
const CustomListPlugin = ListPlugin.configure({
inputRules: [
BulletedListRules.markdown({ variant: '-' }),
OrderedListRules.markdown({ variant: '.' }),
TaskListRules.markdown({ checked: false }),
],
inject: {
targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote, KEYS.codeBlock],
},
render: {
belowNodes: BlockList,
},
});import {
BulletedListRules,
OrderedListRules,
TaskListRules,
} from '@platejs/list';
import { ListPlugin } from '@platejs/list/react';
import { KEYS } from 'platejs';
import { BlockList } from '@/components/ui/block-list';
const CustomListPlugin = ListPlugin.configure({
inputRules: [
BulletedListRules.markdown({ variant: '-' }),
OrderedListRules.markdown({ variant: '.' }),
TaskListRules.markdown({ checked: false }),
],
inject: {
targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote, KEYS.codeBlock],
},
render: {
belowNodes: BlockList,
},
});Add IndentPlugin.configure(...) when you need custom targets, offsets, or units. An explicit Indent configuration overrides the dependency defaults and still installs only one Indent runtime plugin.
API Reference
Scoped Reads
Read list state from the List plugin portal:
const list = editor.plugin(ListPlugin);
list.api.isActive(ListStyleType.Disc);
list.api.isActive([ListStyleType.Disc, ListStyleType.Circle]);
list.api.isActive(KEYS.listTodo);const list = editor.plugin(ListPlugin);
list.api.isActive(ListStyleType.Disc);
list.api.isActive([ListStyleType.Disc, ListStyleType.Circle]);
list.api.isActive(KEYS.listTodo);isActive returns false when the editor has no selection.
Scoped Updates
List updates run in one Plite transaction and create one undo step:
const list = editor.plugin(ListPlugin);
list.update.toggle({ listStyleType: ListStyleType.Decimal });
list.update.indent({ listStyleType: ListStyleType.Disc });
list.update.outdent();const list = editor.plugin(ListPlugin);
list.update.toggle({ listStyleType: ListStyleType.Decimal });
list.update.indent({ listStyleType: ListStyleType.Disc });
list.update.outdent();Pass at to target a location without moving the selection:
list.update.toggle({ at: [2], listStyleType: ListStyleType.Disc });
list.update.toggle({
at: {
anchor: { path: [2, 0], offset: 0 },
focus: { path: [4, 0], offset: 3 },
},
listStyleType: ListStyleType.Decimal,
});list.update.toggle({ at: [2], listStyleType: ListStyleType.Disc });
list.update.toggle({
at: {
anchor: { path: [2, 0], offset: 0 },
focus: { path: [4, 0], offset: 3 },
},
listStyleType: ListStyleType.Decimal,
});- No
at: targets the current selection. PathorPoint: targets one block.Range: targets every matching block in the range.- Missing, invalid, or root locations do nothing.
Update Options
type ToggleListOptions = {
at?: Location;
getSiblingListOptions?: GetSiblingListOptions<Element>;
listRestart?: number;
listRestartPolite?: number;
listStyleType: ListStyleType | string;
};
type IndentListOptions = {
at?: Location;
listStyleType?: ListStyleType | string;
};
type OutdentListOptions = {
at?: Location;
};type ToggleListOptions = {
at?: Location;
getSiblingListOptions?: GetSiblingListOptions<Element>;
listRestart?: number;
listRestartPolite?: number;
listStyleType: ListStyleType | string;
};
type IndentListOptions = {
at?: Location;
listStyleType?: ListStyleType | string;
};
type OutdentListOptions = {
at?: Location;
};listRestart restarts numbering at the target. listRestartPolite applies only when the target begins a list sequence.
Domain Queries
The package exports focused traversal and list-policy queries:
areEqListStyleTypeexpandListItemsWithChildrengetListAbovegetListChildrengetListSiblingsandGetListSiblingsOptionsgetNextListgetPreviousListgetSiblingListandGetSiblingListOptionsgetSiblingListStyleTypeisOrderedList
Hooks
useListToolbarButtonStateanduseListToolbarButtonuseTodoListToolbarButtonStateanduseTodoListToolbarButtonuseTodoListElementStateanduseTodoListElement
The toolbar hooks read and update the scoped ListPlugin API. The todo element hook resolves the element path when the checkbox changes, so it remains correct after node moves.