Markdown Task Lists and What Actually Renders Where
Published 7/2/2025 · 11 min read · Text & language tools
Daniel Okonkwo — Front-end developer and tech writer at OneKitly
Web performance · File formats
Checked against 4 sources
A markdown task list — a list item beginning with [ ] or [x] — is not part of CommonMark. We searched the CommonMark Spec 0.31.2 of 28 January 2024: the phrases "task list" and "checkbox" appear zero times in it. Task lists are defined in the GitHub Flavored Markdown Spec 0.29-gfm of 6 April 2019, section 5.3, as an extension. That is the whole explanation for why the same file shows checkboxes on GitHub and literal brackets elsewhere. We ran one line, "- [ ] buy milk", through four renderers: only the GitHub-flavoured one produced an input element of type checkbox; the CommonMark reference implementation and two other configurations produced a list item containing the literal text [ ] buy milk. The marker rule is exact and unforgiving: an optional run of spaces, a left bracket, either one whitespace character or the letter x in either case, a right bracket, and then at least one whitespace character before the content. "- [x]buy milk" with no space renders as text everywhere, "- []" renders as text, and "- [ ]" with two spaces renders as text. The same caution applies to tables, strikethrough and autolinks, which are also extensions, and to footnotes, which are in neither specification.
Task lists are not in CommonMark. They are a GitHub Flavored Markdown extension, which is why the same file shows checkboxes in one place and literal brackets in another. The exact marker rule, what nesting does, and a table of what is CommonMark, what is GFM and what is neither — checked against both specs and four renderers.
One line, four renderers, two different documents
We took a single line — a hyphen, a space, an empty bracket pair, a space, some text — and rendered it four ways. With GitHub-flavoured parsing on, it became a list item containing an input element of type checkbox, disabled. With the same library and the flavour switched off, and with the CommonMark reference implementation, and with a third library in its default configuration, it became a list item containing the characters [ ] followed by the text. Nothing failed. Four correct renderers produced two different documents from one file.
The reason is documented, not mysterious. We searched the CommonMark Spec 0.31.2, dated 28 January 2024, for "task list" and for "checkbox": both appear zero times. The GitHub Flavored Markdown Spec 0.29-gfm, dated 6 April 2019, has a section 5.3 titled "Task list items (extension)". The word extension is doing all the work in that title: GFM is CommonMark plus five named additions, and a renderer that implements only CommonMark is not broken when it prints your checkboxes as brackets. It is correct.
The marker rule, character by character
GFM section 5.3 defines a task list item as a list item whose first block is a paragraph beginning with a task list item marker followed by at least one whitespace character before any other content. The marker is an optional number of spaces, a left bracket, either a whitespace character or the letter x in lowercase or uppercase, and a right bracket. Everything in that sentence is load-bearing, and the failures are silent: we ran each violation and every one of them rendered as ordinary text in all four renderers.
Omit the space after the closing bracket and nothing happens: "- [x]buy milk" is text. Put two spaces between the brackets and nothing happens: the marker holds exactly one character. Leave the brackets empty and nothing happens. Use any letter other than x and nothing happens. Move the marker away from the start of the paragraph — "- buy [ ] milk" — and nothing happens. Drop the list entirely and "[ ] buy milk" is a paragraph. What does work is more generous than people expect: the letter may be capital X, the bullet may be a hyphen, an asterisk or a plus, the list may be ordered, and a tab counts as the whitespace after the bracket.
Nesting, and the first-block rule
Task lists nest arbitrarily, and the specification shows it with a worked example. We ran a parent item with two indented children and got a checkbox on the parent, a nested list inside the same list item, and a checkbox on each child. Mixing checked and unchecked items with ordinary bullet items in the same list is also fine: the plain item stays plain and the marked ones become checkboxes, which is what makes a checklist readable as a mixed agenda.
The requirement that the first block be a paragraph is easy to trip over. Put a blockquote first — "- > [ ] quoted" — and no renderer produces a checkbox, including the GitHub-flavoured one, because the first block is a blockquote and the marker never gets its chance. Put a second paragraph after the marked one and the checkbox survives, sitting before the first paragraph, with the second paragraph following inside the same item. These are not renderer quirks; they follow directly from the sentence in section 5.3.
The neighbouring extensions, and one that is in no specification
Tables are section 4.10 of the GFM spec, also an extension. Our three-line pipe table came out as a real table element under GitHub-flavoured parsing and under one other library's default settings, and as a single paragraph of literal pipe characters under the CommonMark reference implementation. Strikethrough is section 6.5, and it is stranger than it looks: the spec allows one or two tildes, so ~there~ is struck through, but three or more are not, so ~~~not~~~ stays literal. One library we tested struck through with two tildes but not one, and emitted a strikethrough element rather than the deleted-text element the GFM spec shows. Two renderers can both support strikethrough and still disagree on the input and on the output.
Autolinks come in two kinds and only one is core. An address in angle brackets is CommonMark and became a link in every renderer we ran. A bare address is section 6.9 of GFM, an extension with its own rules: the scheme http is inserted in front of a www address, and trailing punctuation is excluded from the link, so "Visit www.commonmark.org." links the address and leaves the full stop outside it. Only the GitHub-flavoured configuration produced those links; the others left the text alone.
Footnotes are the instructive case, because they are in neither specification. The word footnote appears exactly once in the CommonMark spec and exactly once in the GFM spec, in the same historical sentence of the introduction about implementations that added conventions for footnotes and tables. All four of our renderers left [^1] as literal text and turned the definition line into a stray paragraph. GitHub itself renders footnotes, which is exactly the trap: a feature you have seen work is not therefore in the format.
"Markdown" is not one format
Two specifications with different version numbers and five years between them, plus a per-library set of options, is not a single format. The disagreements go well below the level of features. Asked to render a hard line break, two of our renderers emitted a self-closing break tag and two emitted the HTML5 form. Given a script element in the source, two passed it straight through into the output and one escaped it as text; the GFM spec has a whole extension, section 6.11, that filters nine specific tags by replacing their opening angle bracket with an entity. None of this is a bug in any of them.
The practical rule that follows is short: write to the renderer you actually target, and know which one that is. A README on a code host, a documentation site generator, a chat client and a static site builder are four different targets with four different feature sets, and a document that renders correctly in one of them carries no guarantee about the others. If you are writing for an unknown destination, stay inside CommonMark: headings, emphasis, lists, code, links, angle-bracket autolinks and block quotes are the same everywhere.
Test the round trip, and count from the source
The cheapest test of a checklist is arithmetic. We wrote a five-item nested sprint list with two items marked done, counted them from the markdown source with a pattern matching the marker at the start of a list item, and got 2 of 5. Rendering the same document with GitHub-flavoured parsing and counting the checked input elements in the HTML also gave 2 of 5. Rendering it with the CommonMark reference implementation gave zero input elements — the document is intact, the checkboxes simply were never a feature of that dialect.
That is the habit worth keeping: count from the source, not from the rendering. The markdown file is the record; the HTML is one interpretation of it, and which interpretation you get depends on a library version and a flag. Our checklist generator writes the GFM marker exactly as section 5.3 specifies it — bullet, space, bracket, one character, bracket, space — so the file works where the extension is supported and degrades to a readable bracketed list where it is not. If you need the checkbox to survive anywhere, the honest alternative is a plain list with the word done in it.
| Feature | In CommonMark 0.31.2? | In the GFM spec 0.29? | CommonMark reference output | markdown-it default output |
|---|---|---|---|---|
| Task list - [ ] / - [x] | No — 0 mentions | Yes — section 5.3, extension | Literal [ ] in the list item | Literal [ ] in the list item |
| Pipe table | No | Yes — section 4.10, extension | One paragraph of literal pipes | A real table element |
| Strikethrough with two tildes | No | Yes — section 6.5, extension | Literal tildes | A strikethrough element, but s rather than del |
| Strikethrough with one tilde | No | Yes — one or two tildes | Literal tildes | Literal tildes — it requires two |
| Bare URL turned into a link | No | Yes — section 6.9, extension | Plain text | Plain text in the configurations we ran |
| Autolink in angle brackets | Yes — part of the core spec | Yes, inherited | A real link | A real link |
| Footnote [^1] | No | No — not in the spec either | Literal [^1] and a stray definition line | Literal [^1] and a stray definition line |
| Raw HTML passed through | Yes, by default | Yes, minus nine filtered tags | Passed through unchanged | Escaped as text |
Frequently asked questions
- Why do my checkboxes show as [ ] instead of boxes?
- Because the renderer implements CommonMark without the GitHub extensions. Task lists are section 5.3 of the GFM spec and appear nowhere in CommonMark 0.31.2. The second possibility is a malformed marker: the space after the closing bracket is required, exactly one character goes between the brackets, and the marker must start the first paragraph of the list item. All of those failures render as literal text with no warning.
- Does the x have to be lowercase?
- No. The GFM spec says the letter x in either lowercase or uppercase, and we confirmed that both produce a checked box. Any other letter does not: an o between the brackets rendered as literal text in every renderer we tried. Nor may there be two characters — a marker with two spaces between the brackets is not a marker.
- Can task lists be nested, or used in a numbered list?
- Both. The GFM spec shows arbitrarily nested task lists in a worked example, and our run produced a checkbox on the parent and on each indented child. An ordered list item works identically — the marker sits after the number and the full stop. What does not work is a list item whose first block is not a paragraph: a blockquote before the marker suppressed the checkbox in every renderer.
- Are tables and strikethrough safe to use anywhere?
- No. Both are GFM extensions, sections 4.10 and 6.5, and neither is in CommonMark. Our pipe table came out as a real table in two configurations and as a paragraph of literal pipes in another. Strikethrough is worse: the GFM spec strikes text wrapped in one or two tildes but not three, while another widely used library required two and produced a different HTML element for the result.
- Are footnotes part of GitHub Flavored Markdown?
- Not according to the published GFM specification. The word footnote appears in it exactly once, in a historical sentence of the introduction, and there is no footnote section. GitHub's own site renders them anyway, which is a good illustration of the difference between a specification and a product. All four renderers we tested left the reference as literal text and the definition as a stray paragraph.
Articles you may find interesting
All guides →Related tools
Sources
- CommonMark — CommonMark Spec, version 0.31.2 (2024-01-28) — the core grammar, which contains no task lists, tables, strikethrough or footnotes
- GitHub — GitHub Flavored Markdown Spec, version 0.29-gfm (2019-04-06) — sections 4.10 Tables, 5.3 Task list items, 6.5 Strikethrough, 6.9 Autolinks, 6.11 Disallowed Raw HTML
- CommonMark — The CommonMark project — reference implementations and the Dingus for testing a document against the core spec
- GitHub Docs — Basic writing and formatting syntax — the features GitHub renders beyond its own published specification, including footnotes
Spotted a mistake in this article?