Skip to content
OneKitly

Markdown Task Lists and What Actually Renders Where

Published 7/2/2025 · 11 min read · Text & language tools

Daniel Okonkwo

Daniel OkonkwoFront-end developer and tech writer at OneKitly

Web performance · File formats

Checked against 4 sources

View profile
In short

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.

What each specification defines and what four renderers actually produced, run in Node 26.3.0 with marked 18.0.7, the commonmark reference implementation 0.31.2 and markdown-it 15.0.0 in two presets. "GFM" here means marked with gfm enabled; "markdown-it default" is its out-of-the-box configuration.
FeatureIn CommonMark 0.31.2?In the GFM spec 0.29?CommonMark reference outputmarkdown-it default output
Task list - [ ] / - [x]No — 0 mentionsYes — section 5.3, extensionLiteral [ ] in the list itemLiteral [ ] in the list item
Pipe tableNoYes — section 4.10, extensionOne paragraph of literal pipesA real table element
Strikethrough with two tildesNoYes — section 6.5, extensionLiteral tildesA strikethrough element, but s rather than del
Strikethrough with one tildeNoYes — one or two tildesLiteral tildesLiteral tildes — it requires two
Bare URL turned into a linkNoYes — section 6.9, extensionPlain textPlain text in the configurations we ran
Autolink in angle bracketsYes — part of the core specYes, inheritedA real linkA real link
Footnote [^1]NoNo — not in the spec eitherLiteral [^1] and a stray definition lineLiteral [^1] and a stray definition line
Raw HTML passed throughYes, by defaultYes, minus nine filtered tagsPassed through unchangedEscaped as text
Markdown checklist generatorTurn a list of lines into a Markdown task list (- [ ] item).Try the tool

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
GuideConverting Between List Formats Without Losing Data: The Quoting Rules Nobody ReadsTurning a newline list into a comma list is trivial until an item contains a comma. RFC 4180's quoting rules, why a CSV field may contain a newline, why European spreadsheets use the semicolon, and what an empty item does to a round trip — every case run and printed.GuideStripping Markdown: What Plain Text Loses, and What a Regex Gets WrongA link becomes text with its destination deleted, a nested list loses its hierarchy, a table becomes a row of words. Then the technical half: markdown has no single spec, and a regex stripper mangles a filename, a multiplication sign and the inside of a code block — all shown against a real parser.ExplainerWhere a Line May Break: The Unicode Algorithm Behind Every Wrapped Paragraph"Break at spaces" fails in most of the world's writing systems. UAX #14 gives every character a line-break class; we looked ours up in Unicode 17.0.0 and ran a conforming implementation over no-break spaces, soft hyphens, zero-width spaces, URLs, Japanese and Thai.How-toFiltering Lines by a Pattern Without a Command LineThis is grep for people who do not use grep, with one important difference: the match is a plain substring, so a real regular expression returns an empty box and no error. Every claim here was checked by running the tool.ExplainerCounting Words Is Ambiguous, and Every Tool Answers DifferentlyA word count is a definition, not a measurement. We counted the same paragraph four ways and got 25, 28, 33 and 38 — then counted 50,000 characters of ordinary prose and got agreement to within 4.5%. The gap is entirely driven by compounds, figures and URLs.GuideFormatting Numbers for Six Languages: Separators, Currency and the Parse Back1,234.56 and 1.234,56 are the same number, and confusing them changes the value a reader parses. We ran Intl.NumberFormat for all six site locales and printed every separator — including the invisible one French uses — then measured why parseFloat cannot undo any of it.

Related tools

Sources

Spotted a mistake in this article?