HTML to Markdown Converter
Turn HTML into GitHub-flavoured Markdown — headings, nested lists, code fences and pipe tables — with everything Markdown cannot say named and counted.
Setext underlines only h1 and h2; h3–h6 fall back to ATX. Indented code blocks carry no language, so any language is dropped. Kept constructs stay as raw HTML tags; dropped ones keep their text and lose the tags. Either way they are named and counted.
Markdown cannot say everything HTML can. Anything without a Markdown spelling is kept as raw HTML or dropped — either way it is named and counted below. This reads your markup, not a rendered page: no CSS is applied and nothing is fetched. Line breaks are written as a trailing backslash, which survives editors that trim spaces. Converting back to HTML will not always return your original markup — attributes, classes and inline styles have no Markdown form.
What this converts
| HTML | Markdown |
|---|---|
| h1–h6 | # … ###### … (ATX), or an underline for h1/h2 in setext mode |
| p | a paragraph, one blank line between blocks |
| br | a trailing backslash hard break |
| em, i | *text* or _text_ |
| strong, b | **text** |
| del, s, strike | ~~text~~ |
| code | `code`, in a backtick run longer than any run inside it |
| pre, pre > code, textarea | a ``` fence with the language from class="language-…" or lang, or a 4-space block |
| a[href] | [text](url "title"), <url> when the text is the URL, or [text][n] in reference mode |
| img |  |
| blockquote | > on every line; nesting composes |
| ul, ol, li | - or * bullets and N. numbers, with <ol start> honoured |
| li > input[type=checkbox] | - [ ] and - [x] task-list items |
| hr | --- on its own line |
| table, thead, tbody, tr, th, td | a GFM pipe table, aligned from align or style="text-align" |
| div, section, article, header, footer, main, nav, aside, figure, span | unwrapped — attributes, classes and inline styles have no Markdown form |
| script, style, head, template, noscript, svg, iframe, form, object | dropped with their contents, named and counted |
| comments | dropped and counted |
| dl, sup, sub, abbr, video and anything else with no Markdown spelling | kept as raw HTML, or unwrapped when "drop" is chosen — named and counted either way |
Press ? for shortcuts
Do it offline:$
How to use it
- Paste your markup, or drop an .html file onto the left pane — the file is read locally and never uploaded.
- Pick the dialect: ATX or setext headings, dash or asterisk bullets, fenced or indented code, asterisk or underscore emphasis, inline or reference links, and whether markup with no Markdown form is kept or unwrapped.
- Read the right pane, then check the counters under it — blocks, links, tables, fences, decoded character references, and every dropped or kept construct.
- Copy the Markdown, save it as a .md file, or share the permalink — a whole page outgrows a link, so save the state file.
Paste a page's HTML and read GitHub-flavoured Markdown beside it. Headings, nested lists, links, images, code fences and pipe tables all have a spelling here, and the constructs that do not — a definition list, a styled span, a script — are either kept as raw markup or removed, then named and counted beneath the output. Conversion happens on this device.
Examples
<article><h1>Release notes</h1><p>The converter walks the <a href="https://spec.commonmark.org/0.31.2/">CommonMark spec</a> and prints what it can.</p><ul><li>Headings become ATX hashes<ul><li>Nested items indent to the marker width</li></ul></li><li>Images keep their alt text</li></ul><p><img src="/chart.png" alt="Coverage chart" title="Coverage"></p></article># Release notes
The converter walks the [CommonMark spec](https://spec.commonmark.org/0.31.2/) and prints what it can.
- Headings become ATX hashes
- Nested items indent to the marker width
- Images keep their alt text
Everything here is default: hash headings, dash bullets, an inline link. The article wrapper contributes nothing and disappears; the child list indents by two spaces because that is exactly the width of the marker above it; the image keeps both its alternative text and its title.
<table><thead><tr><th align="left">Option</th><th align="right">Default</th></tr></thead><tbody><tr><td align="left">headings</td><td align="right">atx</td></tr><tr><td align="left">code</td><td align="right">fenced</td></tr></tbody></table><pre><code class="language-js">const fence = "```";
console.log(fence);
</code></pre>| Option | Default |
| :------- | ------: |
| headings | atx |
| code | fenced |
````js
const fence = "```";
console.log(fence);
````The alignment attributes become the colons in the delimiter row, and the columns are padded by the same table serializer the rest of the site uses. The fence opens with four backticks rather than three because the sample contains a run of three, and the language comes from the code element's class.
<script>window.token="s3cret"</script><p>Rates start at 5 EUR ¬anentity; and a bare * survives.</p><dl><dt>Fence</dt><dd>A run of backticks.</dd></dl><p><span style="color:teal">Colour has <em>no Markdown form</p>Rates start at 5 EUR ¬anentity; and a bare \* survives.
<dl><dt>Fence</dt><dd>A run of backticks.</dd></dl>
Colour has *no Markdown form*Five losses at once, each accounted for: the script goes with its contents, the definition list is kept verbatim because it has no Markdown form, the styled span is unwrapped since a colour cannot be written, the loose asterisk is backslash-escaped so it stays a character, and the emphasis left open at the paragraph end is reported rather than repaired.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
A misspelling can still decode
HTML matches the longest reference it knows, and a handful remain valid without the closing semicolon. So ¬anentity; is not left alone: ¬ matches, the text becomes ¬anentity;, and a tolerance note records the legacy match. A reference no table knows, such as &fooby;, is reported separately and kept as typed.
Kept markup is re-emitted, minus two things
Markup with no Markdown spelling is written back tag by tag from your original bytes, so attributes and casing survive. Two things are still refused inside it and counted: event-handler attributes and the nine always-removed elements — what we keep must never become a way to smuggle a script through.
The reverse trip lands nearby, not home
Feeding the result back into a Markdown renderer returns similar markup, not identical markup. Class names, identifiers, inline styles, data attributes and merged table cells have no Markdown form at all, and wrappers that carried only layout are gone. What survives is the document, not its clothing.
Common errors
Input is 4,194,305 characters — this tool refuses anything over 4,194,304 characters (4 MB). Convert the file on your own machine instead.- The only hard refusal. Above four megabytes you get a message rather than a truncated document.
`star` is not a bullet style — use `dash` or `asterisk`.- Each of the six settings is spelled out in the permalink, so an edited link can carry a word the tool does not know. The unknown word is quoted back and refused; it is never quietly swapped for a default.
Removed 1 `script` element, with its contents.- One of nine elements taken out whole, alongside style, head, template, noscript, svg, iframe, form and object. Each count reaches you as a chip, so no removal depends on your noticing it.
`<dl>` has no Markdown spelling — 1 element was kept as raw HTML.- Definition lists, superscript, subscript, abbreviations, video and custom elements land here. Markdown permits raw HTML, so the element stands and is tallied; choose unwrap and the same tally reports removed tags instead.
`</p>` closes an element with `<em>` still open at line 1, column 218 — `<em>` is treated as ending here — no bytes are moved.- Real pages come with broken nesting. The tokenizer closes what the browser would close, points at the column where it happened, and carries on converting.
1 `on…` event-handler attribute was removed from the raw HTML that was kept — Markdown output never carries a script.- The one byte-level edit made to markup that is otherwise reproduced exactly. It is counted like every other loss, because a silent removal would break the promise the rest of this page makes.
FAQ
Why is a line break written as a backslash?
CommonMark accepts two spellings for a hard break: two spaces at the end of a line, or one backslash. Editors, formatters and code review tools routinely trim trailing whitespace, which silently deletes the first spelling and merges your lines. The backslash cannot be trimmed away, so it is what a br element becomes.
What happens to cells that span rows or columns?
A pipe table is a fixed grid, so merged cells cannot be expressed. Each cell is printed once in source order, no content is invented to fill the gap, and a note tells you the span was ignored. A table nested inside a cell is flattened to its text for the same reason, also with a note.
Can I paste a whole saved page, head and all?
Yes. The document head, along with scripts, stylesheets, inline svg, iframes, forms, templates, noscript blocks and comments, is removed with its contents and counted, leaving the body prose. Nothing referenced by the page is fetched — only the characters you pasted are read.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Convert again, whatever the input size.
- ⌘/Ctrl + S
- Save the Markdown pane as a .md file.
- ⌘/Ctrl + C
- Copy all of the Markdown when nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy all of it even when a selection exists.
- t
- Step through the theme palettes, outside a text field.
- ? or ⌘/Ctrl + /
- Show the list of bindings.
- Esc
- Dismiss the frontmost dialog, leaving both panes intact.