Todo lists

Redactor supports to-do lists and checklists — tasks with checkboxes you can tick off in the editor. Items can be nested (sub-tasks inside a task, or bullet lists under a todo).

This guide covers how to add todos, convert formats, HTML/JSON input and output, templates, styling completed items, and reordering.

Add a todo list#

Addbar

Click Add (+) in the addbar and choose Todo. Redactor inserts a checklist with one empty item.

Format menu

Place the caret in a paragraph (or other block) and open Format on the toolbar. Choose Todo — the block becomes a checklist item. Works the other way too: select a todo item and convert it to Text, Heading, Quote, etc.

API

app.block.insert({
    type: 'todo',
    data: {
        items: [
            { content: 'Buy milk' },
            { content: 'Ship release', done: true }
        ]
    }
});

How it works in the editor#

Inside the editor, each todo is a <ul> with data-block="todo". Each item is a <li> with a checkbox and an editable text area:

<ul data-block="todo" class="todo-list">
    <li data-block="todo-item" data-checked="0">
        <input type="checkbox">
        <div class="todo-text" contenteditable="true">Buy milk</div>
    </li>
    <li data-block="todo-item" data-checked="1">
        <input type="checkbox" checked>
        <div class="todo-text" contenteditable="true">Done task</div>
    </li>
</ul>
  • data-checked="1" — item is checked; "0" — unchecked.
  • .todo-text — editable label (can contain inline formatting).
  • Click the checkbox to toggle; todo:change event fires (see Events).

Enter inside an item adds a new todo below (same as list behavior). Backspace at the start of an empty item merges or removes the line.

HTML input and output#

Redactor uses a compact marker syntax in HTML so you can store todos without checkbox markup. This keeps export portable and easy to parse on your backend.

Input — load HTML with markers

When you set content or paste HTML, mark items in a <ul>:

Marker Meaning
[x] Checked (case-insensitive x)
[ ] Unchecked (space inside brackets)
<ul>
    <li>[x] Item checked</li>
    <li>[ ] Item</li>
    <li>[ ] Another item</li>
</ul>
const app = Redactor('#entry', {
    content: `
        <ul>
            <li>[x] Review PR</li>
            <li>[ ] Write docs</li>
        </ul>
    `
});

Redactor detects todo lists by marker text or by existing checkbox + .todo-text markup.

Nested lists inside a todo item are supported:

<ul>
    <li>[ ] Parent task
        <ul>
            <li>[x] Sub-task done</li>
            <li>[ ] Sub-task open</li>
        </ul>
    </li>
</ul>

Output — getHtml()

Exported HTML uses the same marker format. The list wrapper gets the class from todo.todoClass (default todo-list):

<ul class="todo-list">
    <li>[x] Item checked</li>
    <li>[ ] Item</li>
</ul>

data-block attributes are stripped on export — your published page only needs the <ul> / <li> structure and markers (or your own rendering).

JSON data#

Load todos via the data option, setJson(), or API insert. Use done: true for checked items (not checked).

const app = Redactor('#entry', {
    data: [
        {
            type: 'todo',
            data: {
                items: [
                    { content: 'Item checked', done: true },
                    { content: 'Item' },
                    { content: 'Another item', done: false }
                ]
            }
        }
    ]
});

Legacy shape { blocks: [ … ] } also works — fromJSON accepts both.

Item fields

Field Type Description
content string Task HTML (or plain text)
done boolean Checked state
items array Nested sub-items (todo or list children)

Nested tasks

{
    type: 'todo',
    data: {
        items: [
            {
                content: 'Launch',
                done: false,
                items: [
                    { content: 'QA sign-off', done: true },
                    { content: 'Deploy', done: false }
                ]
            }
        ]
    }
}

See Set & get JSON for the full block format.

Todo settings#

All options live under todo:

const app = Redactor('#entry', {
    todo: {
        todoClass: 'todo-list',
        toggleClass: 'todo-done',
        templateInput: '<input type="checkbox">',
        templateContent: '<div></div>',
        template: false
    }
});

todo.todoClass

Default: 'todo-list'

CSS class on the exported <ul>:

todo: { todoClass: 'checklist' }
<ul class="checklist">
    <li>[ ] Buy milk</li>
</ul>

todo.toggleClass

Default: false

Class added to the <li> when checked. Useful for strikethrough, muted color, etc.

todo: { toggleClass: 'todo-checked' }
.todo-checked .todo-text {
    text-decoration: line-through;
    opacity: 0.6;
}

In the editor, the class is toggled on the <li> when the checkbox changes. On HTML export, checked state is still encoded as [x]; data-checked and toggleClass apply in the editor DOM.

todo.templateInput

Default: '<input type="checkbox">'

HTML for the checkbox control. Must include <input type="checkbox"> (can be wrapped):

todo: {
    templateInput: '<label class="todo-check"><input type="checkbox"></label>'
}

todo.templateContent

Default: '<div></div>'

HTML wrapper for the editable label. Redactor always adds the todo-text class and contenteditable on the content node:

todo: {
    templateContent: '<span class="todo-label"></span>'
}

Result in the editor: <span class="todo-label todo-text" contenteditable="true">…</span>.

todo.template

Default: false

Full layout template for each item. Placeholders:

Placeholder Replaced with
$input Checkbox (from templateInput)
$content Editable label (from templateContent)
todo: {
    template: '<label class="todo-row">$input $content</label>'
}

Editor markup:

<li data-checked="0">
    <label class="todo-row">
        <input type="checkbox">
        <div class="todo-text" contenteditable="true"></div>
    </label>
</li>

Use template when you need a single wrapper (<label>, flex row, custom icon slot) around both parts.

Reorder items#

Drag a single item

Hover the left control on a todo line (block handle). Drag up or down to reorder within the list.

Drag the whole list

Click the control handle, then choose Parent in the control bar to select the entire todo list. Drag it to move the checklist with all items relative to other blocks (paragraphs, headings, another list).

Related commands: block.setParent with to: 'nearest' or to: 'top' (control bar Parent / Parent top).

Nested lists

You can nest bullet/numbered lists or another todo under an item. Drag handles work per list level; use Parent to select the list container you want to move.

Events#

app.on('todo:change', () => {
    // Checkbox toggled or todo text edited
    const html = app.getHtml();
    saveDraft(html);
});

See todo:change.