The problem: you have HTML, your system wants Markdown
You copied an article from a CMS, scraped a docs page, or inherited a folder of .html files — and your new system (a static-site generator, a GitHub README, a Notion import, a markdown-based wiki) wants .md. Paste the HTML and you get a wall of tags nobody will read. Rewrite it by hand and you lose the structure you already have. The fix is a converter that walks the HTML's DOM tree and emits the Markdown equivalent for each node — and the catch is that HTML carries more than Markdown can express, so something always gets eaten.
Fastest path
Open the HTML to Markdown Converter, paste the HTML, read the Markdown on the right.
Input: <h2>Quick Start</h2>
<p>Install with <code>npm i</code>. See <a href="https://example.com">docs</a>.</p>
<ul><li>Fast</li><li>Free</li></ul>
Output: ## Quick Start
Install with `npm i`. See [docs](https://example.com).
- Fast
- Free
That's the whole interaction. Paste, read, copy. The conversion runs in the browser — the tool parses the HTML with the DOM API, walks the element tree, and emits Markdown for each node. The Options tab has four style toggles that change the output; the defaults are sane. The rest of this guide is what the converter preserves, what it eats, and when to flip the options.
The substance: the DOM is the intermediate, and that decides everything
The converter doesn't regex your HTML. It hands the string to the browser's DOMParser, which builds a real DOM tree — the same tree a browser renders — and then walks that tree node by node, emitting Markdown for each element. This matters for two reasons.
Malformed HTML gets fixed for you. Unclosed tags, mismatched nesting, attributes without quotes — the browser's parser is forgiving and produces a clean tree anyway. <p><b>bold<p>still bold becomes <p><b>bold</b></p><p>still bold</p> in the DOM, and the converter emits two paragraphs with bold intact. You don't have to tidy the HTML first.
The tree is the structure. Headings, lists, tables, blockquotes, code blocks, links, images, inline formatting — everything that's a DOM element gets converted. Everything that's an attribute the DOM keeps but Markdown can't express gets dropped. That's the lossy part, and it's the part to understand before you convert a 4,000-word doc and wonder where your styling went.
What's preserved, and what's eaten
| Preserved (lossless) | Eaten (lossy) |
|---|---|
| Headings h1–h6 | class, id, style attributes |
| Paragraphs, line breaks | data-* attributes |
Bold (**), italic (*), strikethrough (~~) |
Semantic wrappers (<article>, <nav>, <section>) — become plain block text |
| Inline code and fenced code blocks | Table cell alignment (text-align, align attribute) |
| Links (inline or reference) | colspan and rowspan — each cell becomes its own pipe cell |
| Images with alt text | <div> and <span> wrappers — content kept, wrapper gone |
| Ordered and unordered lists, nested | Form elements (<input>, <select>, <button>) |
| Blockquotes | <script> and <style> blocks |
| Tables (GFM pipe syntax) | <details>/<summary> — content kept, the disclosure widget is gone |
The pattern: Markdown has no syntax for CSS, no syntax for element attributes beyond href/src/alt/title, and no syntax for interactive elements. If your HTML's meaning is in its structure, you lose nothing. If your HTML's meaning is in its styling or its attributes, you lose all of it.
The four style options, and when to flip them
Bullet style — -, *, or +. All three render identically in every Markdown renderer. Pick whichever your team's style guide mandates; - is the most common default and the tool's default. There's no functional difference.
Heading style — ATX (# Heading) or Setext (underline). ATX works for all six levels and is the modern standard. Setext only works for h1 and h2 (h1 gets === underline, h2 gets ---); h3 through h6 fall back to ATX even in Setext mode. Use ATX unless you're targeting a renderer that specifically wants Setext — it's rarer and less readable in source.
Link style — inline [text](url) or reference [text][1] with a numbered list of URLs at the bottom. Inline is the default and what most people want. Reference mode is for link-dense prose: it deduplicates repeated URLs (the same href reuses the same reference number), which shrinks the file and makes the source readable when a paragraph has eight links. The downside is the reference list at the bottom, which is noise if you only have two links.
Code block style — fenced (triple backticks) or indented (four spaces). Fenced is the modern default and the only one that supports syntax highlighting — the tool reads the class="language-javascript" from <code> and emits ```javascript so renderers highlight it. Indented code blocks can't carry a language tag, so you lose highlighting. Use fenced unless you're exporting to a renderer that doesn't support GFM.
The unsupported-tag toggle
The fifth option — "Strip unsupported tags" vs "Keep raw HTML" — decides what happens to elements the converter has no Markdown for. Strip mode (the default) extracts the text content and drops the tag: <mark>highlighted</mark> becomes highlighted. Keep mode leaves the raw HTML in place: <mark>highlighted</mark> stays as <mark>highlighted</mark> in the Markdown. Most renderers pass raw HTML through, so keep mode preserves more information at the cost of messier source. Strip mode gives you cleaner Markdown at the cost of lost semantics.
Gotchas
- The DOM parser is forgiving, but not psychic. It will close unclosed tags and reorder misplaced elements, but it won't infer meaning. A
<div>that was visually a heading because it hadfont-size: 24pxstays a<div>— and becomes plain paragraph text with no heading level. If the HTML's structure relies on CSS rather than tags, the Markdown comes out flat. - Tables lose alignment and spans. GFM pipe tables have no syntax for cell alignment (
:---:) beyond what the converter doesn't emit, andcolspan/rowspandon't exist — every cell becomes its own pipe cell, and merged cells get duplicated or split. A complex layout table becomes a mess; a simple data table converts cleanly. - Reference links are deduplicated by href + title. Two links to the same URL with different titles get two reference entries. Two links to the same URL with the same title share one entry. The numbering is in order of first appearance.
- Escaping is aggressive on inline text. The tool backslash-escapes
\*_{}#+-.!>inside text nodes so they don't get interpreted as Markdown. A literal1. Itemin your HTML text becomes1. Item` in the output — correct, but it looks odd in source. This is the right behavior; unescaped, it would render as an ordered list item. <pre>without<code>still works. The converter looks for a<code>child for the language class; if there isn't one, it uses the raw text content of the<pre>. You lose the language tag but keep the code block.- Nested lists indent two spaces per level. That's the CommonMark standard. Some renderers want four spaces; the output won't break in those, but deeply nested lists may render flat in strict four-space renderers.
- Images keep
alt,src, andtitle. Thetitlebecomes the"title"after the URL in. Width and height attributes are dropped — Markdown has no syntax for them. If you need sized images, keep raw HTML mode for those tags or post-process. - Live conversion can be slow on huge inputs. The converter runs on every keystroke via a memoized
useMemo. Paste a 100KB HTML document and you'll feel the pause. For one-shot conversions of large documents, paste and wait — the result appears when the parse finishes.
Summary
- The converter parses HTML with the browser's
DOMParser, walks the DOM tree, and emits Markdown per node. Malformed HTML gets fixed by the parser; the structure that's in the DOM is what you get. - What's preserved: headings, paragraphs, lists, tables, blockquotes, code blocks, links, images, inline bold/italic/strikethrough/code. What's eaten: classes, styles, IDs, data attributes, semantic wrappers, table alignment and spans, form elements, scripts.
- Four style options: bullet (
-/*/+, cosmetic), heading (ATX vs Setext — ATX is modern and works for all levels), link (inline vs reference — reference deduplicates and helps link-dense prose), code block (fenced vs indented — fenced preserves the language tag for highlighting). - Strip vs keep unsupported tags decides whether unknown elements become plain text or stay as raw HTML in the Markdown. Strip is cleaner; keep preserves more.
- Convert at the HTML to Markdown Converter; for the reverse direction use Markdown to HTML, for table-specific work use Markdown Table Generator, and clean up casing afterward with Text Case Changer.