DEV Community

frank
frank

Posted on

Markdown Dialects Need a Capability Matrix, Not Just a Name

Two tools can both claim to support “Markdown” and still disagree about tables, task lists, bare URLs, and even the HTML element used for strikethrough.

That is usually not a parser bug. It is a contract problem.

“Markdown support” often collapses three separate layers into one label:

  1. a base syntax such as CommonMark;
  2. extensions such as GitHub Flavored Markdown;
  3. platform-specific configuration, sanitization, and post-processing.

I ran a small differential test to make those boundaries concrete.

Fixed environment

Tested on August 15, 2026:

  • commonmark 0.31.2
  • markdown-it 14.3.0, default preset, no third-party plugins
  • marked 18.0.7, default options

The comparison records generated HTML, not screenshots. That distinction matters: two results can look similar while producing different DOM structures for accessibility, export, or later transforms.

Minimal setup:

import * as commonmark from "commonmark";
import markdownit from "markdown-it";
import { marked } from "marked";

const source = "before ~~deleted~~ after";
const reader = new commonmark.Parser();
const writer = new commonmark.HtmlRenderer();

console.log(writer.render(reader.parse(source)));
console.log(markdownit().render(source));
console.log(marked.parse(source));
Enter fullscreen mode Exit fullscreen mode

These are versioned defaults, not permanent labels for the libraries. Options and plugins can change the result.

1. A pipe table is not base CommonMark

Input:

| A | B |
|---|---|
| 1 | 2 |
Enter fullscreen mode Exit fullscreen mode

Observed structure:

Parser Result
commonmark 0.31.2 One paragraph; pipes remain text
markdown-it 14.3.0 A table with thead and tbody
marked 18.0.7 A table with thead and tbody

CommonMark 0.31.2 does not define pipe tables. GFM adds them as an extension.

When I need a controlled table input for a compatibility test, I use a Markdown table generator to avoid accidental delimiter mistakes, then verify the output with the actual target renderer. Generating valid source is not the same as proving platform compatibility.

2. A pipe inside a code span is a hard case

Input:

| A | B |
|---|---|
| `x|y` | a\|b |
Enter fullscreen mode Exit fullscreen mode

With the tested markdown-it and Marked defaults, the unescaped pipe inside the code span still splits the row. One cell receives the opening backtick plus x; the next receives y plus the closing backtick, instead of one code span.

GFM’s table examples require escaping that pipe even inside a code span:

| A | B |
|---|---|
| `x\|y` | a\|b |
Enter fullscreen mode Exit fullscreen mode

This is why splitting a table row with source.split('|') is not a parser.

3. A table-like shape can fail correctly

Input:

| A | B |
| 1 | 2 |
Enter fullscreen mode Exit fullscreen mode

All three parsers emitted a paragraph. Without the delimiter row, none guessed that the author intended a table.

For editor diagnostics, checking for a real table node is safer than checking whether the source contains pipes.

4. Strikethrough has two kinds of differences

For:

before ~~deleted~~ after
Enter fullscreen mode Exit fullscreen mode
  • commonmark keeps the tildes as text;
  • markdown-it emits <s>;
  • Marked emits <del>.

For the boundary case:

before ~not deleted~ after
Enter fullscreen mode Exit fullscreen mode

only Marked recognized strikethrough in this test.

There are two contracts here: whether the syntax is recognized, and which HTML structure represents it. Screenshot tests can miss the second one.

5. Task lists may be plain text or form controls

Input:

- [x] shipped
- [ ] pending
Enter fullscreen mode Exit fullscreen mode

commonmark and the markdown-it default produced normal list items containing [x] and [ ]. Marked produced disabled checkbox inputs and marked the first one as checked.

Whether a host then makes those boxes interactive is a UI and security decision, not something the Markdown syntax alone can decide.

6. Angle-bracket autolinks and bare URLs are different features

All three parsers linked this CommonMark form:

<https://example.com/a_(b)>
Enter fullscreen mode Exit fullscreen mode

But for:

Visit https://example.com/a_(b).
Enter fullscreen mode Exit fullscreen mode

only Marked linked the bare URL under the tested defaults. It also excluded the trailing period from the destination.

markdown-it can add this behavior with linkify; that option was deliberately left off so the matrix describes the default preset.

7. Raw HTML policy is not HTML safety

Input:

<xmp>**not emphasis**</xmp>
Enter fullscreen mode Exit fullscreen mode

commonmark and Marked preserved the xmp element. markdown-it escaped the tags because raw HTML is disabled by default. All three still parsed the emphasis inside.

GFM has a tag-filter extension for names such as script, style, iframe, and xmp. That filter is not a complete HTML sanitizer. Marked’s own documentation also warns that its output is not sanitized.

A production pipeline therefore needs two explicit answers:

  • What HTML can the parser generate?
  • What HTML does the sanitizer allow?

The resulting capability matrix

For these exact versions and defaults:

Capability commonmark markdown-it Marked
Angle-bracket autolink Yes Yes Yes
Pipe tables No Yes Yes
~~x~~ strikethrough No Yes Yes
~x~ strikethrough No No Yes
Task-list checkboxes No No Yes
Bare-URL autolinking No No Yes
Raw HTML enabled by default Yes No Yes
Complete HTML sanitization No No No

“No” is not automatically a defect. Disabling raw HTML can be a deliberate safety boundary. markdown-it can gain more syntax through plugins. The important point is that version and configuration belong in the contract.

A more useful declaration

Instead of only storing:

{ "flavor": "gfm" }
Enter fullscreen mode Exit fullscreen mode

an application could expose something closer to:

{
  "base": "commonmark-0.31.2",
  "extensions": {
    "tables": true,
    "strikethrough": "single-or-double-tilde",
    "taskList": true,
    "extendedAutolink": true,
    "tagfilter": false
  },
  "rawHtml": "parse-then-sanitize"
}
Enter fullscreen mode Exit fullscreen mode

That object is testable, versionable, and much harder to misunderstand than a dialect name.

The open question is whether Markdown ecosystems should standardize a machine-readable capability manifest—or whether every platform will continue documenting its dialect through examples and surprises.

Top comments (0)