HTML to Markdown and back, built on a real HTML tree and a CommonMark parser rather than regular expressions, with GitHub tables, a live preview, and a report of everything Markdown could not carry
Initializing in your browser…
Markdown editor with a live preview from a real CommonMark 0.31.2 parser, scored 580 of 652 against the specification, plus GitHub tables, task lists and strikethrough, a document outline, and .md and .html export
Build or paste a Markdown table and see it read back cell by cell, with column widths measured in display columns so CJK text, emoji and accents line up, and pipes escaped in the right order
Convert between the formats a browser can really write, then open the output and name the format its first bytes say it is. AVIF, BMP and GIF are disabled because a canvas returns a PNG for all three.
You are moving a page into a static site and the HTML has a nested list, a table and some prose that happens to contain asterisks.
HTML
<ul><li>one<ul><li>one a</li></ul></li><li>two</li></ul> <p>keep *these* asterisks</p>
Markdown
- one - one a - two keep \*these\* asterisks
The previous converter gave `- oneone a` for that list, because it matched the open and close tags with a lazy pattern and the inner list closed the outer one; and it left the asterisks unescaped, so converting back produced emphasis the document never had. This one builds a real tree and escapes what Markdown would read as syntax, which is what lets the round trip come back with the same document: 21 of 21 corpus documents now come through where 10 did.
Convert HTML to Markdown and Markdown back to HTML. The question a converter has to answer is whether the Markdown means the same as the HTML, and the way to answer it is to convert back and compare: HTML to Markdown, Markdown to HTML, and the two documents held up against each other. Measured that way on a 21 document corpus on 2026-09-03, the version this replaces came through with everything asked of it on 10 of them; this one manages 21, and 18 of the 18 that CAN round trip come back with the same shape. The other three hold something Markdown has no syntax for, and the tool says which.
The version this replaces parsed HTML with one regular expression per element: `md.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, ...)` and so on down to h6, finishing by stripping whatever tags were left. That cannot work, and the failures are not subtle ones. A lazy match between an open tag and a close tag takes the FIRST close tag, so an element nested inside another of the same name closes the outer one. `[^>]*` says a `>` inside an attribute value ends the tag, so a `title="a > b"` loses its second half. And a `<pre>` was treated as prose and reflowed, which stops code being code.
What that produced, measured on the corpus: a nested list came out as `- oneone a`, a list with `start="5"` came out starting at 1, a block quote holding two paragraphs came out as `> ` followed by two loose lines, a table came out as `ABC123` with no table syntax at all, `café` came out with the entity still in it, an image lost its title, a link whose URL held a space came out broken, and nothing at all was escaped, so HTML text saying `*not emphasis*` became emphasis on the way back.
This version builds a TREE. The tokenizer knows a `>` inside an attribute is not a tag boundary, that a comment holding markup is not markup, that a `<script>` holds raw text so a close tag inside a string is not a close tag, and that HTML closes an open `<p>` when another starts. Every character reference is decoded to the character it names. Every character that would be read as Markdown syntax is escaped, so the round trip does not invent emphasis. A code span holding a backtick gets a longer fence, which CommonMark section 6.1 requires. A destination holding a space gets the angle bracket form. A `<pre>` keeps every character it had.
The other direction goes through the same CommonMark 0.31.2 parser the Markdown Editor uses, which scores 580 of the specification's own 652 examples, rather than through a second set of patterns. That means reference links, setext headings, tight and loose lists and hard line breaks all work, and it means a `javascript:` destination is refused and reported rather than turned into a working link.
**Where Markdown cannot carry something, the tool says so instead of guessing.** A table cell spanning two columns has no Markdown syntax, so that table stays HTML and the page names it. A `<span>`, a `<kbd>`, a `<video>`: there is no syntax for any of them, and the three ways of handling one (keep the HTML, keep only its text, drop it) all lose something, so it is a setting rather than a silent default. Two switches that used to be offered, "preserve links" and "preserve images", were never read by any code in the file and are gone.
Convert exported HTML into Markdown for a static site, and see straight away what the conversion could not carry rather than finding out later.
Paste a page and get Markdown whose lists nest properly, whose tables are tables, and whose code blocks still look like code.
Switch direction to see the HTML a CommonMark parser produces from your Markdown, including the reference links and the destinations it refuses.
Because the converter matched `<ul>...</ul>` with a lazy pattern, and a lazy match stops at the FIRST close tag: the inner list closed the outer one. Measured, `<ul><li>one<ul><li>one a</li></ul></li><li>two</li></ul>` came out as `- oneone a`. This version builds a tree, so nesting is nesting.
You choose, because every answer loses something. Keeping the HTML is faithful but leaves markup in your Markdown; keeping only the text loses the element; dropping it loses the text as well. The default keeps the HTML, and the page lists every element it did that for.
Yes, in GitHub table syntax, with per-column alignment taken from the header cells. A cell that spans two columns or two rows cannot be written in that syntax at all, so a table containing one is kept as HTML and named on the page rather than silently flattened.
Because it contained characters Markdown reads as syntax. HTML text saying `*not emphasis*` is text, and writing it into Markdown unescaped turns it into emphasis the moment anything renders it. The escaping is what makes the round trip come back with the same document.
No, and it no longer uses regular expressions either. The HTML side is a tokenizer and a tree written for this project; the Markdown side is the CommonMark 0.31.2 parser the Markdown Editor uses, which reproduces 580 of the specification's own 652 examples. Everything runs in your browser.
Your text is processed locally in the browser. Nothing you paste or open is transmitted or logged.