All posts

Markdown Checkboxes and Task Lists: The Complete Guide

How to write a markdown checkbox with dash-bracket syntax, where task lists render and where they don't, how to nest them, and the small mistakes that quietly break them.

Short answer: A markdown checkbox is a list item that begins with - [ ] for unchecked or - [x] for checked. The space inside the brackets is required, and so is the space after the closing bracket — - [ ] Buy milk. Task lists are a GitHub Flavored Markdown extension, so they render on GitHub, GitLab, Obsidian, and most modern editors, but not in strict CommonMark and not in Discord or Reddit.

Task lists are the feature that turns a markdown file into something you actually work from. A README becomes a setup checklist. A pull request description becomes a review gate. A daily note becomes a to-do list you can tick off. The syntax is four characters long, which is exactly why the failure mode is so annoying: get one space wrong and you get a bullet with literal square brackets in it, with no error message explaining why.

Here's the full syntax, where it works, and every way it breaks.


The Syntax

- [ ] Unchecked task
- [x] Completed task
- [X] Also completed — uppercase X works too

Which renders as:

  • Unchecked task
  • Completed task
  • Also completed — uppercase X works too

The rules, precisely:

  1. It has to be a list item. Start the line with -, *, or + followed by a space. All three bullet markers work.
  2. The checkbox comes first. [ ] or [x] must be the very first thing in the list item's content. You can't put a checkbox in the middle of a line.
  3. A space inside the brackets for unchecked. [ ], not [].
  4. No space inside for checked. [x], not [ x ].
  5. A space after the closing bracket. - [ ] Task, not - [ ]Task.

Ordered lists work too in most parsers that support the extension:

1. [x] Draft the spec
2. [ ] Review with the team
3. [ ] Ship it

GitHub renders ordered task lists as checkboxes. Some parsers only implement the extension for bullet lists, so if the output looks wrong in a tool you don't control, fall back to -.


It's GFM, Not CommonMark

This trips people up constantly. The original Markdown spec from 2004 has no concept of a checkbox. Neither does CommonMark, the standardization effort that most modern parsers implement as their core. Task lists came from GitHub, and they live in the GitHub Flavored Markdown spec as an extension called "task list items."

Practically, that means support is a per-tool question, not a "markdown" question. A strict CommonMark renderer will happily give you a bullet list where each item starts with a literal [ ].

WhereRenders as checkboxClickable
GitHub — issues, PRs, commentsYesYes, and clicking edits the source
GitHub — .md files in a repoYesNo, static
GitLab — issues, MRs, commentsYesYes
ObsidianYesYes
VS Code previewYesNo
PandocYes, with the task_lists extensionN/A
Static site generatorsOnly with a GFM plugin (remark-gfm, markdown-it-task-lists)No
DiscordNo — renders as literal bracketsNo
RedditNoNo
SlackNo — Slack uses its own limited syntaxNo
NotionConverts to native to-do blocks on paste/importYes, as Notion blocks
OpenMarkYesYes, writes back to the file

If you're writing for a platform not on this list, paste three lines into a preview and check before you commit to the format.


Nested Task Lists

Indent a task under another task to create a sub-list. Two spaces is enough for GitHub and most parsers:

- [ ] Launch checklist
  - [x] Write release notes
  - [x] Bump version numbers
  - [ ] Upload screenshots
  - [ ] Submit for review
- [ ] Post-launch
  - [ ] Reply to reviews

Which renders as:

  • Launch checklist
    • Write release notes
    • Bump version numbers
    • Upload screenshots
    • Submit for review
  • Post-launch
    • Reply to reviews

Two things worth knowing:

Parents don't auto-check. Checking every child leaves the parent unchecked. No markdown parser tracks that relationship — it's a text format, not a task manager.

Strict parsers want the content column. The safe indentation is however many characters precede the parent's text. For - that's two spaces. For 10. that's four. GitHub is forgiving here; some other parsers are not, and under-indenting silently produces a flat list instead of a nested one.


Task Lists Inside Tables Don't Work

This is the most common thing people try that has no clean answer. A GFM pipe table cell holds inline content only — emphasis, code spans, links, images. Lists are block-level, so - [ ] inside a cell renders as literal text:

| Task | Status |
|------|--------|
| Write docs | - [ ] |

That produces a cell containing the characters - [ ]. Not a checkbox.

Raw HTML doesn't rescue you either, at least not on GitHub: <input type="checkbox"> isn't on GitHub's HTML allowlist, so the sanitizer strips it and you're left with an empty cell.

The workarounds:

  • Unicode symbols. and (or and ) render everywhere and cost nothing. They're not interactive, but neither is a table.
  • Move the checklist out of the table. Put the task list above or below it.
  • Use a status word. Done / Pending in a column is more readable in a table anyway, and it survives every renderer.

For everything else tables can and can't do, see the markdown tables syntax guide.


Progress Counters and Interactive Checkboxes on GitHub

Task lists in a GitHub issue or pull request body aren't just formatting. GitHub parses them and shows a completion counter on the issue — the number of checked items over the total — and that counter follows the issue into list views and project boards. Checking a box in the rendered issue updates the underlying markdown for everyone.

A few behaviors worth knowing:

  • Clicking works in issues, PRs, and comments, but not in repository files. A task list in a README.md renders as checkboxes but they're read-only in the repo view. To change one, you edit the file.
  • You need write access to the repo to tick a box in an issue.
  • A task item that is just an issue reference- [ ] #412 — renders as that issue's title, and stays in sync with whether the issue is closed.
  • Leading parentheses need escaping. - [ ] (Optional) Add tests confuses the parser; write - [ ] \(Optional) Add tests.

More GitHub-specific markdown behavior is covered in markdown for GitHub.


Common Mistakes

What you typedWhat rendersFix
-[ ] TaskA paragraph of literal text — with no space, it isn't a list item at all- [ ] Task
- [] TaskBullet with literal []Put a space inside: - [ ]
- [ ]TaskBullet with literal [ ]TaskSpace after the bracket
- [ x ] TaskBullet with literal brackets- [x] Task
– [ ] TaskNothing list-like — macOS smart dashes turned -- or a hyphen into an en dashTurn off smart dashes, or retype the hyphen
Four-space-indented first itemA code blockRemove the indentation
- [ ] (Optional) thingBroken item on GitHubEscape it: \(Optional)
Checkboxes in Discord or RedditLiteral bracketsNot supported; use bullets or emoji

The smart-punctuation one deserves emphasis. If you draft in Notes, Mail, or any app with substitutions enabled and paste into a .md file, your hyphens may arrive as en dashes and your quotes as curly quotes. The markdown looks right and parses as nothing.


Clickable Checkboxes in a Real Editor

Static rendering is fine for reading. It's less fine when the file is your to-do list.

OpenMark renders task lists in Document view as real, clickable checkboxes. Click one and it writes the change straight back to the source file — [ ] becomes [x] on disk, and the file stays plain text you can commit, sync, or open in anything else. Switch to Markdown view when you want to edit the text itself.

That combination is the point of task lists in the first place: a checklist you can act on that's still just a text file. No database, no proprietary format, no sync service required. If you keep daily notes or project checklists as markdown, see how to preview markdown on Mac for the other options and how they compare.


Related Syntax

Task lists sit next to a few other features that behave in unexpected ways:


Download OpenMark → — $9.99, one-time, native macOS. Task lists render as checkboxes you can actually click, and every tick is saved right back into your markdown file.