The CommonMark Edge Cases That Break Editors
Reference: CommonMark Spec 0.31.2 + Babelmark — the ground-truth spec and the divergence tool. Every edge case below was reproduced across five real parsers on 2026-07-11.
Open one Markdown file in four editors and you can get four different results. A ~~strikethrough~~ becomes <del> in one engine, <s> in another, and stays as literal tildes in a third, from the exact same input. The reason is structural, not a bug: Markdown was never a single language, and its parsers implement different, incompatible flavors of it.
That is an uncomfortable fact for a format whose entire pitch is portability. Markdown is supposed to be the plain-text you can move anywhere. Mostly it is. But the moment you reach past a heading and a bold word, into tables, task lists, footnotes, raw HTML, or a hard line break, you leave the part everyone agrees on and enter the part where every implementation quietly makes its own decision. This post shows exactly where that happens, reproduced against the CommonMark reference implementation, GitHub's GFM, Pandoc, markdown-it, and marked.
Why does the same Markdown render differently in different editors?
Because there is no one Markdown. John Gruber published the original in 2004 as a syntax description and a Perl script, not a formal grammar1. In the decade that followed, "dozens of implementations were developed in many languages"1, each resolving the ambiguities its own way. Your editor is not wrong; it just chose a different flavor.
The CommonMark project, started by John MacFarlane (who also wrote Pandoc), says this plainly in its own rationale. "Because there is no unambiguous spec, implementations have diverged considerably. As a result, users are often surprised to find that a document that renders one way on one system (say, a GitHub wiki) renders differently on another (say, converting to docbook using pandoc)."1 That sentence is the whole problem, admitted by the people trying hardest to fix it.
The original design goal was human comfort, not machine precision. Gruber's stated philosophy: "Markdown is intended to be as easy-to-read and easy-to-write as is feasible."2 And the load-bearing line: "Readability, however, is emphasized above all else. A Markdown-formatted document should be publishable as-is, as plain text, without looking like it's been marked up with tags or formatting instructions."2 A spec optimized for a human reading raw text is, almost by definition, a spec with soft edges — and soft edges are where parsers drift.
What CommonMark actually standardized
CommonMark is the closest thing to a Markdown standard, and it is exhaustive by design: version 0.31.2 pins its behavior with 652 worked test cases in its machine-readable spec, where every one is an input paired with the exact HTML output it must produce3. That test suite, not the prose, is the real spec.
You can count them yourself; the number matters because it is easy to get wrong:
curl -s https://spec.commonmark.org/0.31.2/spec.json \
| python3 -c 'import json,sys; print(len(json.load(sys.stdin)))'
# 652
Those 652 cases define a common core: headings, emphasis, links, images, blockquotes, fenced code, lists, thematic breaks, entities, backslash escapes. Where two engines both implement CommonMark, they agree on this core — that is the point of a conformance suite. The trouble is that the core deliberately leaves out most of what people actually write in 2026: tables, strikethrough, checkboxes, footnotes, math. CommonMark drew a small, precise circle. Everything interesting happens outside it.
Why did early implementations need a reference suite at all? Because the first one was buggy. The CommonMark authors are blunt about the origin: "In the absence of a spec, early implementers consulted Markdown.pl to resolve these ambiguities. But Markdown.pl was quite buggy, and gave manifestly bad results in many cases, so it was not a satisfactory replacement for a spec."1 The 652 test cases exist to replace a broken Perl script as the oracle of truth.
How the flavors fork
The cleanest way to picture the divergence is as a single source that fans out. Every parser runs roughly the same first two phases (split the document into block structures, then parse inline spans inside them), and then applies its own extension layer. The extension layer is where two engines reading the same bytes produce different HTML.
flowchart TD
SRC[One .md source] --> BLK[Block parse<br/>lists, code fences]
BLK --> INL[Inline parse<br/>emphasis, links]
INL --> CORE{Which flavor?}
CORE -->|CommonMark| C1[652 core rules<br/>tables stay text]
CORE -->|GitHub GFM| C2[+ tables, strike,<br/>tasks, autolinks]
CORE -->|Pandoc| C3[+ footnotes,<br/>math, def lists]
C1 --> O1[HTML A]
C2 --> O2[HTML B]
C3 --> O3[HTML C]
Figure: One Markdown source, three outputs. Every engine block-parses then inline-parses the same text; the fork is the extension layer. CommonMark applies only its 652 core rules and leaves tables as literal text; GitHub's GFM adds tables, strikethrough, task lists, and autolinks; Pandoc adds footnotes, math, and definition lists. Same input, three different HTML trees.
GitHub Flavored Markdown is the most-used flavor on Earth, and it defines itself relative to the core. "GFM is a strict superset of CommonMark."4 Superset is the operative word: every extension GFM adds — "Tables (extension)", "Strikethrough (extension)", "Task list items (extension)", "Autolinks (extension)", "Disallowed Raw HTML (extension)"4 — is a named bolt-on. On GitHub the bolts are always present. In a plain CommonMark editor they do not exist, so your table is just a line of pipes.
Pandoc's markdown reader adds a different, larger set — including footnotes, definition_lists, and tex_math_dollars5, none of which appear anywhere in CommonMark. Two "Markdown" files can therefore be mutually unreadable: a Pandoc document full of $...$ math and [^1] footnotes is not valid CommonMark, and a CommonMark document is not guaranteed to survive a GFM-only renderer intact.
The edge cases, reproduced across five parsers
To make this concrete instead of theoretical, I installed five real implementations and ran the same inputs through each: the CommonMark reference (commonmark.js), GitHub-Flavored Markdown (via pandoc -f gfm, Pandoc's GFM reader), Pandoc's own markdown, markdown-it, and marked. The results below are literal output, not paraphrase.
The single most revealing input is strikethrough, because it produces three different answers:
Input: ~~struck~~
CommonMark ref -> <p>~~struck~~</p> (literal — not an operator)
GitHub GFM -> <p><del>struck</del></p>
Pandoc markdown -> <p><del>struck</del></p>
markdown-it -> <p><s>struck</s></p> (different element!)
marked -> <p><del>struck</del></p>
Read that carefully. CommonMark does not recognize ~~ at all, so it passes the tildes through as text. GFM, Pandoc, and marked produce <del>, the semantic "deleted text" element. And markdown-it produces <s>, the "no longer relevant" element. If your CSS styles del but not s, a strikethrough that looks fine on GitHub renders unstyled in a markdown-it editor. One input, three verdicts.
Tables split more cleanly — CommonMark against everyone else:
Input: | A | B |
|---|---|
| 1 | 2 |
CommonMark ref -> <p>| A | B | ...</p> (a paragraph of pipes)
GitHub GFM -> <table>...</table>
Pandoc markdown -> <table>...</table>
markdown-it -> <table>...</table>
marked -> <table>...</table>
The table is invisible in strict CommonMark and correct everywhere else. This is the most common "why is my table broken?" support ticket in existence, and the answer is always the same: the renderer is running CommonMark core with the tables extension turned off.
Task lists behave the same way, but with a twist in the markup:
Input: - [ ] todo
- [x] done
CommonMark ref -> <li>[ ] todo</li> (literal brackets)
markdown-it -> <li>[ ] todo</li> (literal brackets)
GitHub GFM -> <li><label><input type="checkbox">todo</label></li>
Pandoc markdown -> <li><label><input type="checkbox">todo</label></li>
marked -> <li><input disabled type="checkbox"> todo</li>
Three outcomes again: literal text (CommonMark, markdown-it), a <label>-wrapped checkbox (GFM, Pandoc), and a bare disabled checkbox with different attributes (marked). If you wrote automation that scrapes input[type=checkbox] out of rendered notes, it works against GitHub and silently returns nothing against a default markdown-it build.
The raw-HTML edge case is a security decision in disguise
The sharpest divergence is not a formatting quirk — it is what an engine does with raw HTML you didn't write, and it exposes each parser's safety posture. Feed the same <div> wrapping some Markdown to all five, and one of them refuses to trust you.
Input: <div>
**bold?** and *em?*
</div>
CommonMark ref -> <div> <p><strong>bold?</strong> ...</p> </div>
GitHub GFM -> <div> <p><strong>bold?</strong> ...</p> </div>
Pandoc markdown -> <div> <p><strong>bold?</strong> ...</p> </div>
marked -> <div><p><strong>bold?</strong> ...</p></div>
markdown-it -> <p><div></p> <p><strong>bold?</strong> ...</p> <p></div></p>
Four engines pass the <div> straight through into the output. markdown-it escapes it to <div> — because it ships with raw HTML disabled by default (html: false). That is not a parsing accident; it is a deliberate safe-by-default choice, because raw HTML in untrusted Markdown is a cross-site-scripting vector. The lesson for anyone rendering Markdown from users: your parser's default HTML policy is a security boundary, and the flavors disagree on where it sits. Rendering the same note "faithfully" can mean rendering an attacker's <script> faithfully too.
Even the humble hard line break diverges. CommonMark defines two ways to force a <br>: end a line with two spaces, or with a backslash3. But the output element differs: the reference and GFM emit XHTML-style <br />, markdown-it emits HTML5 <br>, and marked collapses adjacent soft-wrapped lines differently. And the trailing-two-spaces form is fragile in a way the spec cannot help with: most editors strip trailing whitespace on save, so the break you typed is gone before the parser ever sees it.
Footnotes: when the product outruns the spec
Footnotes are the case where the ecosystem is most confusing, because the tooling has moved ahead of every published spec. The [^1] syntax appears in neither CommonMark nor the GFM specification — yet Pandoc renders it, and GitHub.com renders it on issues and pull requests, while GitHub wikis do not6. Reproduced:
Input: Here is a claim.[^1]
[^1]: The evidence.
CommonMark ref -> <p>Here is a claim.[^1]</p> <p>[^1]: The evidence.</p>
markdown-it -> <p>Here is a claim.[^1]</p> (literal)
marked -> <p>Here is a claim.[^1]</p> (literal)
Pandoc markdown -> <sup>1</sup> + <section class="footnotes">...</section>
So the same Markdown footnote is a rendered reference in Pandoc and on a GitHub PR, invisible plain text in a stock markdown-it editor, and — the real trap — invisible on a GitHub wiki even though it works one click away on a GitHub issue6. The syntax is a de facto convention that most serious tools implement and no standard blesses. If your notes lean on footnotes, you are betting on your specific renderer's extension list, not on "Markdown."
There is one more class worth naming, because it looks like a divergence but is actually a precise, unforgiving rule. Nested-list indentation is governed exactly by CommonMark: a child item's indent must match the width of the parent's marker. Under 1. (three columns), a child indented three spaces nests; a child indented two spaces does not — and every engine I tested agrees, because they all implement the same rule. It feels random until you know it. It is not a flavor difference; it is a rule people simply don't know.
Babelmark: the ground truth for actual divergence
When you need to know how a specific snippet renders everywhere at once, don't reason about it — measure it. Babelmark, maintained by the CommonMark authors, sends one input to a fleet of live Markdown implementations and shows every output side by side. It is the closest thing the ecosystem has to an oracle for "what will actually happen."
It is not a toy fleet, either. When I queried Babelmark's backend for ~~x~~ on 2026-07-11, 38 live implementations responded — across roughly a dozen languages (nine PHP engines, eight Rust, three C, three Kotlin, and pairs in JavaScript, Python, Java, Haskell, Perl, Ruby, and C#)7. Thirty-eight programs, one input, and — for anything past the core — a spread of answers. That number is the real shape of "Markdown": not one language with edge cases, but dozens of dialects that happen to share a vocabulary.
The practical move: before you commit to a syntax that matters (a table layout, a footnote-heavy document, an HTML embed), paste it into babelmark.github.io and see the fan-out for yourself. It turns an argument into an experiment.
How to write Markdown that survives
The takeaway is not "Markdown is broken." It is "Markdown is a family, so write to the part the whole family shares." Concretely, four habits keep your files portable across the 38 engines Babelmark tracks instead of hostage to any one of them, and they cost nothing to adopt today:
- Stay in the CommonMark core when portability matters. Headings, emphasis, links, images, blockquotes, fenced code, and lists render identically everywhere that claims CommonMark. That is your safe subset.
- Treat extensions as a deliberate dependency. Tables, task lists, strikethrough, footnotes, and math are real and useful — just know you are now depending on GFM or Pandoc specifically, and that the file may degrade elsewhere.
- Prefer the backslash hard break over two trailing spaces, since editors strip trailing whitespace and the invisible break is unrecoverable.
- Lint and test. Run a Markdown linter for consistency, and paste ambiguous snippets into Babelmark before you rely on them.
This is the same conclusion, reached from the parser side, that the 24-flavors argument reaches from the essay side: the universal subset is the real portable format, and everything above it is a negotiated extra. It is also why keeping notes as plain Markdown files you own beats a proprietary format even with the flavor mess — a divergent render is a cosmetic problem you can re-render, while a locked format is a permanent one.
Frequently Asked Questions
Why does the same Markdown render differently in different editors?
Because Markdown is not one language. Gruber released it in 2004 as a syntax description and a buggy Perl script, not a formal spec1. Different editors implement different flavors — CommonMark, GitHub's GFM, Pandoc — that make incompatible choices about anything beyond the shared core, so the same file produces different HTML.
What is the difference between CommonMark and GitHub Flavored Markdown?
CommonMark is a strict, testable core specification with 652 conformance cases3. GFM is defined as "a strict superset of CommonMark"4 — it adds tables, strikethrough, task lists, and autolinks as named extensions on top. Anything valid in CommonMark is valid in GFM, but GFM documents using extensions are not valid plain CommonMark.
Which Markdown flavor is safe to write in?
The CommonMark core is the safe subset: headings, emphasis, links, images, blockquotes, fenced code blocks, and lists render the same across every conforming engine. Use tables, task lists, footnotes, or math only when you know your target renderer supports them, because those are extensions, not standard Markdown.
Why isn't my table or strikethrough showing up?
Your renderer is almost certainly running CommonMark core with extensions disabled. Tables and strikethrough are not part of CommonMark — a strict engine outputs your table as a literal line of pipes and ~~text~~ as literal tildes4. Switch to a GFM or Pandoc renderer, or enable the relevant extension.
How do I make a hard line break in Markdown?
End the line with a backslash, or with two trailing spaces3. Prefer the backslash: most editors strip trailing whitespace on save, which silently deletes the two-space break before the parser ever sees it. Both produce a <br> where supported.
Is there an official Markdown standard?
Not universally. CommonMark is the most rigorous and widely adopted specification, and GFM builds on it4, but Gruber's original 2004 syntax remains the historical reference and no single body governs all of Markdown1. Dozens of implementations coexist, which is exactly why outputs diverge.
How can I test how my Markdown renders across parsers?
Use Babelmark at babelmark.github.io. It sends your input to a fleet of live implementations and shows every rendering side by side — 38 engines responded when tested on 2026-07-117. It is the fastest way to see whether a snippet is portable or flavor-specific before you commit to it.
Why did my raw HTML get escaped into visible text?
Some parsers disable raw HTML by default for safety. markdown-it, for example, ships with html: false, so a <div> you write becomes <div> in the output. This is a deliberate defense against cross-site scripting in untrusted Markdown, and flavors disagree on where that boundary sits.
The next time a .md file looks wrong in one place and right in another, you are not looking at a bug — you are looking at two parsers implementing two different flavors of a language that never had just one. Write to the core, treat the rest as a dependency, and measure before you commit.
Markdown's portability is real, but it lives in the shared core — which is exactly why mnmnote.com keeps your notes as plain Markdown files you own, readable by any of those 38 engines and the next 38 too.
Footnotes
-
"CommonMark Spec, Version 0.31.2 — §1.2 Why is a spec needed?", John MacFarlane et al., https://spec.commonmark.org/0.31.2/, retrieved 2026-07-11. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
"Markdown: Syntax — Philosophy", John Gruber, Daring Fireball (2004), https://daringfireball.net/projects/markdown/syntax (archived https://web.archive.org/web/20260711103131/https://daringfireball.net/projects/markdown/syntax), retrieved 2026-07-11. ↩ ↩2
-
"CommonMark Spec 0.31.2 (machine-readable test suite, 652 examples)", CommonMark, https://spec.commonmark.org/0.31.2/spec.json, retrieved 2026-07-11. ↩ ↩2 ↩3 ↩4
-
"GitHub Flavored Markdown Spec — §1.1 What is GitHub Flavored Markdown?", GitHub, https://github.github.com/gfm/, retrieved 2026-07-11. ↩ ↩2 ↩3 ↩4 ↩5
-
"Pandoc User's Guide — Extensions (footnotes, definition_lists, tex_math_dollars)", John MacFarlane, https://pandoc.org/MANUAL.html, retrieved 2026-07-11. ↩
-
"Basic writing and formatting syntax — Footnotes", GitHub Docs, https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax, retrieved 2026-07-11. ↩ ↩2
-
"Babelmark — Compare Markdown Implementations", CommonMark, https://babelmark.github.io/ (backend enumerated 38 live implementations for input
~~x~~), retrieved 2026-07-11. ↩ ↩2