List

PreviousNext

Turn any block into a list item.

Loading…

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: ListPlugin installs 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/list
pnpm add @platejs/list

Add 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.
  • Path or Point: 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:

  • areEqListStyleType
  • expandListItemsWithChildren
  • getListAbove
  • getListChildren
  • getListSiblings and GetListSiblingsOptions
  • getNextList
  • getPreviousList
  • getSiblingList and GetSiblingListOptions
  • getSiblingListStyleType
  • isOrderedList

Hooks

  • useListToolbarButtonState and useListToolbarButton
  • useTodoListToolbarButtonState and useTodoListToolbarButton
  • useTodoListElementState and useTodoListElement

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.