Markdown Mastery: Write, Convert & Preview
Markdown is a family of dialects. We ran the 652 CommonMark spec examples and 18 everyday snippets through seven parsers, GitHub's included, to show which syntax works everywhere and what changes between a README, a GitHub comment and a Python script.
Markdown Is a Family of Dialects
John Gruber released Markdown 1.0.1 in December 2004 as two things at once: a plain-text syntax and a Perl script that turned it into HTML. The syntax spread faster than any single definition of it, so "Markdown" now names a family of dialects. CommonMark (spec version 0.31.2, with 652 numbered examples) pins down the core rules. GitHub Flavored Markdown (GFM) adds tables, task lists, strikethrough, bare-URL links and an HTML tag filter on top. Older parsers still follow Gruber's looser rules.
The differences show up the moment a file moves between tools. We ran all 652 CommonMark examples through seven parsers, one of them in two configurations, and compared each output with the HTML the spec expects, after normalizing whitespace:
| Parser | CommonMark 0.31.2 examples matched | Why it differs |
|---|---|---|
| commonmark.js 0.31.2 (reference implementation) | 652 / 652 | |
| markdown-it 14.1.0, CommonMark preset | 652 / 652 | |
| markdown-it-py 4.0.0, CommonMark preset | 652 / 652 | |
| cmark-gfm, GitHub's parser (cmarkgfm 2024.11.20) | 624 / 652 | GFM extensions change 28 outputs, mostly emphasis, HTML blocks and autolinks |
| markdown-it 14.1.0, default preset | 580 / 652 | Raw HTML is off by default, so HTML blocks are escaped |
| marked 4.3.0, as our Markdown Preview and Markdown to HTML call it | 574 / 652 | GFM on, line breaks off; links, entities and list edge cases |
| mistune 3.2.0, default | 466 / 652 | Escapes HTML; its own list and emphasis rules |
| Python-Markdown 3.10.2 | 346 / 652 (362 with Extra + sane_lists) | Follows Gruber's original rules, not CommonMark |
Eighteen Snippets, Six Renderers
Scores hide which constructs break. These snippets show where the dialects split. The GitHub columns come from cmark-gfm, the C library GitHub publishes. GitHub's rendering API was rate-limited from our test machine, so we ran the same library locally: once with footnotes on, as GitHub Docs describe them, and once with hard line breaks, the way GitHub treats comments.
| Input | CommonMark | GitHub .md file | GitHub comment | Our tools (marked 4.3.0) | Python-Markdown 3.10.2 | mistune 3.2.0 |
|---|---|---|---|---|---|---|
#Heading (no space) | paragraph | paragraph | paragraph | paragraph | heading | paragraph |
| Nested list, 2-space indent | nested | nested | nested | nested | flat | nested |
| Nested list, 4-space indent | nested | nested | nested | nested | nested | nested |
| List right after a text line | list | list | list | list | paragraph | list |
The year was then 2. place | paragraph | paragraph | paragraph | paragraph | paragraph | paragraph |
| Pipe table | text | table | table | table | text | text |
- [x] task | text | checkbox | checkbox | checkbox | text | text |
~~old~~ | text | struck | struck | struck | text | text |
Bare https:// URL | text | link | link | link | text | text |
| Single line break | one line | one line | line break | one line | one line | one line |
| Two trailing spaces | break | break | break | break | break | break |
| Backslash at line end | break | break | break | break | backslash shown | break |
```js fence | class="language-js" | <pre lang="js"> | <pre lang="js"> | class="language-js" | not a code block | class="language-js" |
[^1] footnote | text | footnote | footnote | text | text | text |
<details> with Markdown inside | kept, inside parsed | kept, inside parsed | kept, inside parsed | kept, inside parsed | kept, inside not parsed | escaped |
snake_case_name | no emphasis | no emphasis | no emphasis | no emphasis | no emphasis | no emphasis |
some*thing*here | emphasis | emphasis | emphasis | emphasis | emphasis | emphasis |
Setext heading (Title over =====) | heading | heading | heading | heading | heading | heading |
markdown-it's default preset (not shown) matched CommonMark plus tables and strikethrough, but left task lists, bare URLs and footnotes as text and escaped raw HTML unless you pass html: true. The 2. place row is a CommonMark rule, not a bug: an ordered list can interrupt a paragraph only when it starts at 1, so a line that happens to begin with "2." stays part of the sentence in every parser we tested.
Line Breaks Depend on Where You Type
GitHub Docs spell out the split: in issues, pull requests and discussions, GitHub renders a single newline as a line break, while in an .md file the same two lines join into one. In files you need two trailing spaces, a backslash at the end of the line, or a <br> tag. Our cmark-gfm runs agreed, and turning on comment-style hard breaks cost the parser 66 more CommonMark examples (558 instead of 624).
Two trailing spaces worked in every parser we tested, but they are invisible in an editor, and many editors strip trailing whitespace on save. The backslash is visible and part of CommonMark; only Python-Markdown printed it as a literal backslash. Our Markdown Preview and Markdown to HTML tools call marked with line breaks off, so they render a README the way GitHub does. Switching marked to comment-style breaks dropped its CommonMark score from 574 to 514.
Lists and Indentation
In CommonMark, a nested item belongs to its parent when it is indented to the column where the parent's text starts: two spaces under - , three under 1. . Python-Markdown predates that rule and wants four spaces, so our two-space nested list came out flat there. It also refuses to start a list on the line right after a paragraph, which CommonMark, GitHub, marked and mistune all accept.
The safe habit that every parser in the table agreed on: indent nested items by four spaces and leave a blank line before a list.
Shopping:
- Fruit
- Apple
- Pear
- Vegetables
Tables, Task Lists, Strikethrough and Footnotes Are Extensions
None of these four is in CommonMark. The GFM spec (version 0.29-gfm) defines tables, task list items, strikethrough and extended autolinks; GitHub Docs document footnotes separately. Python-Markdown's Extra bundle adds tables, footnotes and fenced code, plus abbreviations, attribute lists, definition lists and Markdown inside HTML, but not task lists or strikethrough, which we confirmed by running the same snippets with Extra on.
A GFM table needs a header row and a delimiter row of hyphens; colons in the delimiter row set alignment, and the pipes at the start and end of each line are optional:
| Tool | Format | Size |
|:--------|:------:|------:|
| Preview | HTML | 12 KB |
The Markdown Table Generator writes the delimiter row and alignment colons for you.
Raw HTML and Safety
CommonMark passes raw HTML through untouched, which is how <details> blocks and <br> tags work inside Markdown. GFM adds a tag filter: it replaces the opening < of nine tags (title, textarea, style, xmp, iframe, noembed, noframes, script and plaintext) with < so they show as text. markdown-it's default preset and mistune escape all HTML instead.
marked does neither: HTML in the input reaches the output. That is why our Markdown Preview runs the rendered HTML through DOMPurify before it shows or copies anything. If your own site renders Markdown that other people write, sanitize the HTML after rendering; escaping the Markdown first breaks code spans and links.
Why READMEs, Notebooks and Prompts Use Markdown
Benjamin Lee's "Ten simple rules for documenting scientific software" (PLoS Computational Biology, 2018, retrieved through PubMed) treats the README as a project's homepage on GitHub, Bitbucket and GitLab and argues it should be readable as raw source, which is why Markdown, reStructuredText or plain text beat HTML there. Lee's rule of thumb: assume the README is the only documentation people read, so it should cover installation, where the full docs live, the license, how to run the tests and a quickstart.
In data analysis, Markdown carries the prose of literate-programming documents. Baumer and Udwin (WIREs Computational Statistics, 2015) describe R Markdown re-running every code chunk each time the document renders, so the report follows the data instead of numbers pasted in by hand, a step they list among the sources of trivial errors. Orozco and colleagues (Journal of Economic Surveys, 2020) recommend the same pattern for empirical economics: R Markdown with knitr and pandoc, or Stata's dyndoc commands since Stata 15. Both papers came from a Wiley Scholar Gateway search.
Markdown is also the default structure for language-model prompts, and the evidence there is mixed. He and colleagues (Microsoft and MIT, 2024, found through alphaXiv) formatted the same prompts as plain text, Markdown, YAML and JSON for four GPT models on six benchmarks. On MMLU, GPT-3.5-turbo scored 59.7% with JSON and 50.0% with Markdown, while GPT-4-1106-preview scored 81.2% with Markdown and 73.9% with JSON. Only 16% of GPT-3.5 answers were identical between the Markdown and JSON versions, and no format won everywhere. Markdown headings in a prompt are a format choice to test against your own task, not a guaranteed gain.
Converting Between Markdown and HTML
Our Markdown to HTML tool uses marked with the same settings as the preview, so its output matches GitHub's file rendering for the constructs above, with two exceptions: fenced code gets a language- class instead of GitHub's lang attribute, and footnotes stay as text. The HTML to Markdown tool parses the input with the browser's own HTML parser and writes tables, nested lists and code-fence languages back out; anything Markdown has no syntax for, such as classes, inline styles or merged table cells, is dropped.
Habits that held up in every parser we tested:
- Put a space after the
#in ATX headings. - Leave a blank line before lists, code fences and tables.
- Indent nested list items by four spaces.
- End a line with a backslash, or use
<br>, when you need a hard break in a file. - Tag fences with a language (
```js) so highlighters and converters know what they hold. - Write image alt text that says what the image shows; WCAG 2.2 success criterion 1.1.1 requires a text alternative for non-text content.
- [1]CommonMark Spec, version 0.31.2 (2024-01-28), 652 examples, checked Oct 14, 2026 ↗
- [2]GitHub Flavored Markdown Spec, version 0.29-gfm (tables, task lists, strikethrough, autolinks, tag filter) ↗
- [3]GitHub Docs, Basic writing and formatting syntax (line breaks, footnotes), checked Oct 14, 2026 ↗
- [4]John Gruber, Markdown 1.0.1 (Daring Fireball, December 17, 2004) ↗
- [5]Python-Markdown 3.10 documentation, Extra extension ↗
- [6]Lee BD (2018), Ten simple rules for documenting scientific software, PLoS Computational Biology 14(12): e1006561 (via PubMed) ↗
- [7]Baumer B, Udwin D (2015), R Markdown, WIREs Computational Statistics 7(3): 167-177 (via Wiley Scholar Gateway) ↗
- [8]Orozco V et al. (2020), How to Make a PIE: Reproducible Research for Empirical Economics and Econometrics, Journal of Economic Surveys 34(5): 1134-1169 (via Wiley Scholar Gateway) ↗
- [9]He J et al. (2024), Does Prompt Formatting Have Any Impact on LLM Performance? arXiv:2411.10541 (via alphaXiv) ↗
- [10]W3C, WCAG 2.2, Success Criterion 1.1.1 Non-text Content ↗