<rss version="2.0">
  <channel>
    <title>Commonmark on LLBBL Blog</title>
    <link>https://llbbl.blog/categories/commonmark/</link>
    <description></description>
    
    <language>en</language>
    
    <lastBuildDate>Sun, 16 Aug 2026 10:00:00 -0500</lastBuildDate>
    
    <item>
      <title>Markdown Is Not a Format, It&#39;s an Argument</title>
      <link>https://llbbl.blog/2026/08/16/markdown-is-not-a-format.html</link>
      <pubDate>Sun, 16 Aug 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/08/16/markdown-is-not-a-format.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve covered PNG and text files, and now it&amp;rsquo;s time for Markdown, which can be thought of as a philosophy of formatting or a lifestyle of text documents more so than an actually well defined file format. It has structure, and it has specifications, plural, and nothing agrees.&lt;/p&gt;
&lt;p&gt;Here is three lines of Markdown run through five parsers:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;INPUT: &amp;#34;- outer\n  - inner\n&amp;#34;

Python-Markdown      &amp;lt;ul&amp;gt; &amp;lt;li&amp;gt;outer&amp;lt;/li&amp;gt; &amp;lt;li&amp;gt;inner&amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt;
markdown2            &amp;lt;ul&amp;gt; &amp;lt;li&amp;gt;outer &amp;lt;ul&amp;gt; &amp;lt;li&amp;gt;inner&amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt;&amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt;
mistune              &amp;lt;ul&amp;gt; &amp;lt;li&amp;gt;outer&amp;lt;ul&amp;gt; &amp;lt;li&amp;gt;inner&amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt; &amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt;
marko (CommonMark)   &amp;lt;ul&amp;gt; &amp;lt;li&amp;gt; outer&amp;lt;ul&amp;gt; &amp;lt;li&amp;gt;inner&amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt; &amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt;
cmark-gfm (GitHub)   &amp;lt;ul&amp;gt; &amp;lt;li&amp;gt;outer &amp;lt;ul&amp;gt; &amp;lt;li&amp;gt;inner&amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt; &amp;lt;/li&amp;gt; &amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Five parsers, five different results. Most of that is cosmetic whitespace, but look at the first one: Python-Markdown produced a &lt;strong&gt;flat list&lt;/strong&gt;. The nesting is gone. That&amp;rsquo;s not a formatting difference, that&amp;rsquo;s a different document.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-original-spec-was-an-essay&#34;&gt;The Original Spec Was an Essay&lt;/h2&gt;
&lt;p&gt;John Gruber released Markdown in March 2004, along with a Perl script called &lt;code&gt;Markdown.pl&lt;/code&gt;. The design goal was stated plainly:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The overriding design goal for Markdown&amp;rsquo;s formatting syntax is to make it as readable as possible. The idea is that a Markdown-formatted document should be publishable as-is, as plain text, without looking like it&amp;rsquo;s been marked up with tags or formatting instructions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That goal was met, and it&amp;rsquo;s why we&amp;rsquo;re all still using it twenty years later. The syntax borrowed from conventions people had already invented for plain text email and Usenet: &lt;code&gt;=&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt; underlines from Setext, &lt;code&gt;#&lt;/code&gt; headers from atx, &lt;code&gt;&amp;gt;&lt;/code&gt; quoting from Usenet, &lt;code&gt;*&lt;/code&gt; for emphasis from Textile and reStructuredText. None of it was new. That was the point.&lt;/p&gt;
&lt;p&gt;What Markdown shipped without was a grammar. The specification was English prose describing the syntax with examples, and the tiebreaker for anything the prose didn&amp;rsquo;t cover was &amp;ldquo;whatever &lt;code&gt;Markdown.pl&lt;/code&gt; does.&amp;rdquo; A Perl script full of regular expressions became the definition of the format by default.&lt;/p&gt;
&lt;p&gt;That works fine until someone writes a second implementation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;where-the-prose-ran-out&#34;&gt;Where the Prose Ran Out&lt;/h2&gt;
&lt;p&gt;The ambiguities weren&amp;rsquo;t exotic. They were things you hit in the first week:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much indentation nests a list?&lt;/strong&gt; Two spaces? Four? One tab? The original prose didn&amp;rsquo;t say clearly, and the answer interacts with the rule that four spaces means a code block.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens inside raw HTML?&lt;/strong&gt; If you write a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and put Markdown inside it, does the Markdown get processed? Gruber&amp;rsquo;s implementation had behavior; the prose didn&amp;rsquo;t specify it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When does a &lt;code&gt;*&lt;/code&gt; open emphasis versus just being an asterisk?&lt;/strong&gt; In &lt;code&gt;a * b * c&lt;/code&gt;, are those multiplication signs or emphasis delimiters?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do underscores work inside words?&lt;/strong&gt; This one bites daily:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;INPUT: &amp;#34;snake_case_variable&amp;#34;

Python-Markdown      &amp;lt;p&amp;gt;snake_case_variable&amp;lt;/p&amp;gt;
markdown2            &amp;lt;p&amp;gt;snake&amp;lt;em&amp;gt;case&amp;lt;/em&amp;gt;variable&amp;lt;/p&amp;gt;
mistune              &amp;lt;p&amp;gt;snake_case_variable&amp;lt;/p&amp;gt;
marko (CommonMark)   &amp;lt;p&amp;gt;snake_case_variable&amp;lt;/p&amp;gt;
cmark-gfm (GitHub)   &amp;lt;p&amp;gt;snake_case_variable&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;markdown2 italicizes your variable name. Every other parser leaves it alone. Both are defensible readings of a spec that never addressed it.&lt;/p&gt;
&lt;p&gt;Or the heading with no space after the hash:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;INPUT: &amp;#34;#Heading&amp;#34;

Python-Markdown      &amp;lt;h1&amp;gt;Heading&amp;lt;/h1&amp;gt;
markdown2            &amp;lt;h1&amp;gt;Heading&amp;lt;/h1&amp;gt;
mistune              &amp;lt;p&amp;gt;#Heading&amp;lt;/p&amp;gt;
marko (CommonMark)   &amp;lt;p&amp;gt;#Heading&amp;lt;/p&amp;gt;
cmark-gfm (GitHub)   &amp;lt;p&amp;gt;#Heading&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Half of them give you a heading, half give you a paragraph starting with a hash. This one matters because &lt;code&gt;#hashtag&lt;/code&gt; at the start of a line is a real thing people write.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;everyone-wrote-their-own&#34;&gt;Everyone Wrote Their Own&lt;/h2&gt;
&lt;p&gt;With no formal spec, every implementation became a dialect, and the popular ones added features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PHP Markdown Extra&lt;/strong&gt; (Michel Fortin, 2005) added pipe tables, definition lists, footnotes, fenced code blocks, and attribute blocks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MultiMarkdown&lt;/strong&gt; (Fletcher Penney, 2005) added metadata frontmatter, cross-references, citations, and LaTeX export.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pandoc Markdown&lt;/strong&gt; (John MacFarlane, 2006) built a real AST-based parser and added YAML frontmatter, TeX math, grid tables, and citations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kramdown&lt;/strong&gt; (Thomas Leitner, 2009) added inline attribute lists and its own math support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each is a superset of a slightly different reading of the original. A document written for one is not guaranteed to render correctly in another, and the failure mode is silent: you don&amp;rsquo;t get a parse error, you get the wrong document.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;commonmark-specify-the-ambiguity-away&#34;&gt;CommonMark: Specify the Ambiguity Away&lt;/h2&gt;
&lt;p&gt;On 3 September 2014, Jeff Atwood announced a spec effort on Coding Horror under the name &lt;strong&gt;Standard Markdown&lt;/strong&gt;, with John MacFarlane as primary author and people from GitHub, Reddit, Stack Exchange, and Meteor involved. The goal was not a new dialect and not a replacement for Gruber&amp;rsquo;s syntax, but an unambiguous description of what the existing syntax should mean in every case.&lt;/p&gt;
&lt;p&gt;The name lasted about a day. That night, by Atwood&amp;rsquo;s account, Gruber emailed him and MacFarlane privately, called the name &amp;ldquo;infuriating,&amp;rdquo; and asked that the project be renamed and the domain taken down. On 4 September, Atwood published a follow-up retitling it &lt;strong&gt;Common Markdown&lt;/strong&gt;, which shortly became the one-word &lt;strong&gt;CommonMark&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Worth being precise here, because this story gets retold badly: this was not a trademark action. Gruber holds no registered trademark on &amp;ldquo;Markdown&amp;rdquo; and did not invoke one. It was an objection to the name, made in private email, and the only public record of his side is Atwood&amp;rsquo;s paraphrase. There is no Daring Fireball post about it.&lt;/p&gt;
&lt;p&gt;The naming fight is a footnote. The approach is the interesting part. Rather than describing the syntax in prose and hoping, CommonMark defines a parsing &lt;em&gt;algorithm&lt;/em&gt; and ships an executable test suite pairing exact input with exact expected HTML, more than 500 examples embedded in the spec document itself. Conformance is not a matter of opinion. You run the tests.&lt;/p&gt;
&lt;p&gt;The algorithm works in two passes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase one walks the document line by line and builds block structure.&lt;/strong&gt; Container blocks (blockquotes, lists, list items) and leaf blocks (headings, code blocks, paragraphs, HTML blocks) get assembled into a tree. Link reference definitions get collected. No inline formatting is considered at all in this phase, which is why block structure always wins: a &lt;code&gt;&amp;gt;&lt;/code&gt; at the start of a line is a blockquote marker regardless of what emphasis you thought you were in the middle of.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase two walks the text inside leaf blocks and resolves inline structure.&lt;/strong&gt; This is where emphasis, links, images, code spans, and inline HTML get parsed, using a delimiter stack.&lt;/p&gt;
&lt;p&gt;That two-phase split is the single most useful thing to know about Markdown parsing, because it explains most surprising behavior. If your emphasis &amp;ldquo;leaked&amp;rdquo; across a list item boundary, it didn&amp;rsquo;t; blocks were decided before emphasis was ever considered.&lt;/p&gt;
&lt;h3 id=&#34;the-emphasis-rules-are-hard&#34;&gt;The Emphasis Rules Are Hard&lt;/h3&gt;
&lt;p&gt;Emphasis is the hardest part of the spec, and CommonMark&amp;rsquo;s solution is a set of flanking rules. A run of &lt;code&gt;*&lt;/code&gt; or &lt;code&gt;_&lt;/code&gt; is classified as &lt;strong&gt;left-flanking&lt;/strong&gt; (can open emphasis) or &lt;strong&gt;right-flanking&lt;/strong&gt; (can close it) based on the characters on either side, roughly: a delimiter can open if it&amp;rsquo;s not followed by whitespace, and can close if it&amp;rsquo;s not preceded by whitespace, with extra conditions around punctuation.&lt;/p&gt;
&lt;p&gt;Then there&amp;rsquo;s a special case for underscores: an &lt;code&gt;_&lt;/code&gt; can open emphasis only if it&amp;rsquo;s left-flanking &lt;strong&gt;and not&lt;/strong&gt; right-flanking. That single asymmetry is what makes &lt;code&gt;snake_case_variable&lt;/code&gt; safe, because the middle underscores are both left- and right-flanking and are therefore disqualified from opening anything. Asterisks don&amp;rsquo;t get that rule, which is why &lt;code&gt;snake*case*variable&lt;/code&gt; still italicizes.&lt;/p&gt;
&lt;p&gt;This is what &amp;ldquo;specifying the ambiguity away&amp;rdquo; costs. The rule isn&amp;rsquo;t elegant. It exists because real documents contain identifiers, and a spec that italicizes your variable names is wrong no matter how clean its grammar is.&lt;/p&gt;
&lt;p&gt;You can see the payoff in the nesting case:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;INPUT: &amp;#34;*foo**bar**baz*&amp;#34;

Python-Markdown      &amp;lt;p&amp;gt;&amp;lt;em&amp;gt;foo&amp;lt;/em&amp;gt;&amp;lt;em&amp;gt;bar&amp;lt;/em&amp;gt;&amp;lt;em&amp;gt;baz&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
everyone else        &amp;lt;p&amp;gt;&amp;lt;em&amp;gt;foo&amp;lt;strong&amp;gt;bar&amp;lt;/strong&amp;gt;baz&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Four parsers agree, and the one that predates the delimiter-stack approach gets it wrong in a way that changes the meaning.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;gfm-is-a-layer-not-a-fork&#34;&gt;GFM Is a Layer, Not a Fork&lt;/h2&gt;
&lt;p&gt;GitHub Flavored Markdown is CommonMark plus five extensions, and it&amp;rsquo;s specified against CommonMark rather than diverging from it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tables&lt;/strong&gt;, pipe-delimited with alignment colons&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task lists&lt;/strong&gt;, &lt;code&gt;- [ ]&lt;/code&gt; and &lt;code&gt;- [x]&lt;/code&gt;, rendered as checkboxes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strikethrough&lt;/strong&gt;, &lt;code&gt;~~text~~&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autolinks&lt;/strong&gt;, bare URLs linkified without brackets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A raw HTML filter&lt;/strong&gt; that neutralizes dangerous tags by escaping their opening bracket&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That last one is a security control rather than a formatting feature, which tells you something about what it&amp;rsquo;s like to run a Markdown renderer on user-submitted content at GitHub&amp;rsquo;s scale.&lt;/p&gt;
&lt;p&gt;The extension boundary is visible if you feed the same table to both:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;INPUT:
| a | b |
|---|---|
| 1 | 2 |

CommonMark  &amp;lt;p&amp;gt;| a | b | |---|---| | 1 | 2 |&amp;lt;/p&amp;gt;
cmark-gfm   &amp;lt;table&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;a&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;b&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tables are not Markdown. Tables are a GFM extension. CommonMark renders that input as a paragraph containing literal pipe characters, and it is correct to do so.&lt;/p&gt;
&lt;p&gt;Tables, footnotes, task lists, strikethrough, frontmatter, math, and Mermaid diagrams are all extensions. None of them are guaranteed anywhere.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-to-do-about-it&#34;&gt;What To Do About It&lt;/h2&gt;
&lt;p&gt;The practical takeaways are short.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Know which parser you&amp;rsquo;re targeting.&lt;/strong&gt; &amp;ldquo;It renders on GitHub&amp;rdquo; tells you about cmark-gfm, and nothing about your static site generator, your docs pipeline, or someone&amp;rsquo;s RSS reader.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prefer the constructs everyone agrees on.&lt;/strong&gt; Headings with a space after the hash, fenced code blocks, asterisks for emphasis, blank lines between blocks, four-space or consistent nesting. Boring Markdown survives transport.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don&amp;rsquo;t rely on parser-specific behavior you discovered by accident.&lt;/strong&gt; If nesting a list at two spaces works in your tool, that&amp;rsquo;s your tool, not the format.&lt;/p&gt;
&lt;p&gt;There is even a formal way to say which dialect you mean. RFC 7763 registers &lt;code&gt;text/markdown&lt;/code&gt; as a media type, and RFC 7764 defines a &lt;code&gt;variant&lt;/code&gt; parameter for exactly this problem:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;text/markdown; variant=CommonMark
text/markdown; variant=GFM
text/markdown; variant=Original
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The standards process looked at Markdown, concluded that saying &amp;ldquo;this is Markdown&amp;rdquo; is not specific enough to be useful, and standardized a way to say which Markdown you meant.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the tradeoff Markdown made. PNG picked one answer and enforced it with a checksum. A text file refuses to answer anything. Markdown let a million answers bloom, got adopted everywhere precisely because it was easy to implement badly, and has spent the last decade trying to agree with itself.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll take that trade. But it&amp;rsquo;s worth knowing that when you write Markdown, you are not writing in a format. You&amp;rsquo;re writing in a dialect, and hoping the reader speaks it.&lt;/p&gt;
&lt;h2 id=&#34;sources&#34;&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://daringfireball.net/projects/markdown/syntax&#34;&gt;Daring Fireball: Markdown&lt;/a&gt; — Gruber&amp;rsquo;s original 2004 syntax document and design goals&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://spec.commonmark.org/&#34;&gt;CommonMark Specification&lt;/a&gt; — the parsing algorithm, emphasis flanking rules, and executable test suite&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://spec.commonmark.org/0.31.2/#appendix-a-a-parsing-strategy&#34;&gt;CommonMark parsing strategy appendix&lt;/a&gt; — the two-phase block/inline design&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.github.com/gfm/&#34;&gt;GitHub Flavored Markdown Spec&lt;/a&gt; — the five extensions, specified against CommonMark&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7763&#34;&gt;RFC 7763&lt;/a&gt; and &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7764&#34;&gt;RFC 7764&lt;/a&gt; — the &lt;code&gt;text/markdown&lt;/code&gt; media type and the registered dialect variants, both by S. Leonard, March 2016&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.codinghorror.com/standard-flavored-markdown/&#34;&gt;Coding Horror: Standard Flavored Markdown&lt;/a&gt; and &lt;a href=&#34;https://blog.codinghorror.com/standard-markdown-is-now-common-markdown/&#34;&gt;Standard Markdown is now Common Markdown&lt;/a&gt; — Atwood&amp;rsquo;s announcement and the rename a day later&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://daringfireball.net/2004/03/introducing_markdown&#34;&gt;Daring Fireball: Introducing Markdown&lt;/a&gt; — the original 15 March 2004 announcement&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/github/cmark-gfm/blob/master/extensions/tagfilter.c&#34;&gt;&lt;code&gt;tagfilter.c&lt;/code&gt; in cmark-gfm&lt;/a&gt; — the nine tags GFM&amp;rsquo;s raw HTML filter neutralizes&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at &lt;a href=&#34;https://micro.blog/llbbl?remote_follow=1&#34;&gt;@logan@llbbl.blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>