I have written Markdown every working day for years — plugin READMEs, changelogs, docs for toolz.dev, release notes for WP Adminify, half my commit messages. And for most of that time I treated the rendering step as magic. You write asterisks, GitHub shows bold. Fine. Move on.
Then I built a docs section that took Markdown out of the database and rendered it into a Next.js page, and the magic turned into a list of very specific decisions I had to make. Does a single newline become a <br>? (GitHub comments say yes. The Markdown spec says no.) Does <div> in the source render as a div or as literal text? (Depends who is asking, and whether you trust the author.) What class goes on a fenced code block so the highlighter picks it up? Why does one parser turn **bold**text into bold and another leaves it alone?
None of that is exotic. It is just the stuff nobody tells you, because Markdown looks so simple that people assume there is nothing under it. There is quite a lot under it. The Markdown to HTML converter on toolz.dev exposes those decisions as switches rather than hiding them, which is the version of this tool I wanted when I was debugging why my line breaks kept disappearing.
TL;DR: Markdown is a writing format; HTML is the display format. Something has to compile one into the other. The rules that trip people up: consecutive lines join into one paragraph unless you end a line with two spaces (or turn on a "breaks" option), raw HTML is either passed through or escaped depending on the parser's trust settings, and GitHub's dialect (GFM) adds tables, task lists, strikethrough and bare-URL autolinking on top of the CommonMark baseline. Fenced code compiles to
<pre><code class="language-js">, which is the hook Prism, highlight.js and Shiki look for. The Markdown to HTML converter does all of this in your browser, with every switch exposed.
What is Markdown, and why does it need converting at all?
Markdown is a plain-text syntax that John Gruber published in 2004, with one stated design goal: a Markdown document should be publishable as-is, readable as plain text, without looking like it has been marked up with tags. That is why the syntax borrows from the conventions people already used in email — asterisks around a word for emphasis, a line of dashes under a heading, a > for a quote.
The consequence is that Markdown is not a rendering format. Nothing displays Markdown. Browsers display HTML, and every place you have ever seen Markdown rendered — GitHub, a static site, a docs portal, a chat app — ran a parser first and put HTML on the screen.
So the conversion has to happen somewhere. Your options are roughly:
- At build time, in a static site generator or bundler. Fine when the content lives in your repo.
- At request time, on a server. Necessary when content comes from a database, costly if you do it on every request without caching.
- In the browser, at the moment you need it. Which is what you want when the answer to "I just need the HTML for this one thing" is a copy-paste, not a build pipeline.
That third case is more common than it sounds. Pasting release notes into a CMS field that only takes HTML. Getting a README into an email template. Checking what a doc will look like before you commit it. Converting a draft written in Obsidian into something you can hand to a designer. None of those justify wiring a parser into a project.
What is the difference between CommonMark and GitHub Flavored Markdown?
Gruber's original spec was a page of prose and a Perl script, and it left enough ambiguity that every implementation disagreed on the edge cases. CommonMark is the answer to that: a strict, testable specification with a conformance suite of hundreds of examples, so that two conforming parsers produce identical output for the same input. It defines the baseline — headings, paragraphs, emphasis, links, images, lists, blockquotes, code blocks, thematic breaks, backslash escapes, HTML blocks.
GitHub Flavored Markdown (GFM) is a formal superset of CommonMark, specified by GitHub, that adds the things people kept asking for:
| Feature | CommonMark | GFM | Syntax |
|---|---|---|---|
| Tables | No | Yes | | a | b | with a | --- | --- | delimiter row |
| Task lists | No | Yes | - [x] done / - [ ] todo |
| Strikethrough | No | Yes | ~~gone~~ |
| Autolinked bare URLs | No | Yes | https://toolz.dev with no brackets |
| Footnotes | No | Yes (GitHub extension) | [^1] |
| Headings, lists, code, emphasis | Yes | Yes | Identical |
If your Markdown came from a GitHub README, a GitLab wiki, a Notion export or most modern editors, it is GFM. Turn GFM on in the converter or your tables will render as literal pipes, which is the number one "the converter is broken" support question anyone who ships one of these tools receives.
Why did my line break disappear?
Because Markdown, following the conventions of plain-text email, treats consecutive non-blank lines as a single paragraph. This:
Line one
Line two
produces <p>Line one\nLine two</p> — one paragraph, and the newline collapses to a space when the browser renders it. It is not a bug; it is the spec, and it exists so you can hard-wrap your prose at 80 columns in a text editor without that wrapping leaking into the output.
There are three ways to get an actual break:
- A blank line starts a new paragraph. This is what you want most of the time.
- Two trailing spaces at the end of a line produce a hard break —
<br />. This is the standard trick, and it is invisible in your editor, which is why people find it maddening. - A backslash at end of line does the same in CommonMark, and is at least visible.
And then there is the fourth way, which is the source of the confusion: many platforms turn on a "breaks" mode where every single newline becomes a <br />. GitHub comments and issues do this. Most chat apps do this. GitHub README files do not. So the same text renders differently in a GitHub issue and in the README of the same repository, which is a genuinely bad piece of design that we are all now stuck with.
The converter exposes this as a switch. If your source was written for a chat-style renderer, turn line breaks on. If it is a document, leave it off and use blank lines like the spec intends.
How is raw HTML handled?
Markdown permits inline HTML — the original spec explicitly says any HTML you write passes straight through. That is a feature when you are the author (you want your <details> block, your <img> with a width attribute, your anchor with a rel), and it is a liability when you are not.
Because if you render untrusted Markdown with HTML pass-through enabled, you have an XSS vulnerability. <script>alert(document.cookie)</script> is valid Markdown. So is <img src=x onerror="...">. So is an <a href="javascript:...">. A comment box, a user profile bio, a public wiki — anywhere strangers write Markdown that other people read — must either escape HTML or sanitise the output with a real sanitiser (DOMPurify is the usual answer, and it is a real sanitiser precisely because a regex is not enough for a hostile input).
This converter defaults to escaping raw HTML: <div> in your source appears as the literal text <div> in the output, exactly as if you had written <div>. You can turn pass-through on when the source is yours. And regardless of that setting, the live preview strips <script>, <style>, inline on* event handlers and javascript: URLs before it renders — a defence in depth measure, so pasting someone else's README into the preview pane cannot execute their code. That is a preview-safety measure, not a general-purpose sanitiser: if you are building a product that renders user Markdown, use a dedicated sanitiser server-side and do not trust a regex, mine included.
If you need to escape a character from Markdown so it renders literally — an asterisk that should stay an asterisk, an underscore in a filename — a backslash does it: \*not emphasis\*. And if you are wrangling entities in the other direction, the HTML Entities encoder/decoder is the tool for that job.
What HTML should a good converter actually emit?
Semantic, boring, class-free HTML — with one exception.
- Headings become
<h1>–<h6>. With heading IDs enabled, each also gets a slugifiedid, de-duplicated when two headings share a title (#setup,#setup-1). That is what makes#anchordeep links work, and it is what a table-of-contents generator hangs off. - A fenced block with an info string —
```js— becomes<pre><code class="language-js">. *This is the exception.** The `language-` class is the convention Prism, highlight.js and Shiki all look for, and it is why the converter emits a class at all. The converter does not colour your code; the highlighter on your page does, and it needs that hook. - Lists become
<ul>/<ol>, and here is a subtlety worth knowing: a tight list (no blank lines between items) puts the text directly inside<li>, while a loose list (blank lines between items) wraps each item's content in<p>. That is CommonMark behaviour, not a quirk, and it is why your list suddenly grows vertical spacing when you add a blank line between two bullets. The CSS is not broken; the HTML genuinely changed. - Tables become real
<table>/<thead>/<tbody>markup, withstyle="text-align:center"on cells when the delimiter row uses:---:. - Task lists become
<input type="checkbox" disabled>inside the<li>, which is exactly what GitHub emits.
Content-first, no wrapper divs, no utility classes. You style it from the outside, with a .prose class or your own rules, and the markup stays portable.
How do I use the converter?
Step 1: Paste the Markdown
Drop in a README, a changelog, release notes, a draft. Output updates as you type — there is no convert button, and nothing is uploaded.
Step 2: Set the switches
GitHub Flavored on if the source has tables, task lists or strikethrough (it probably does). Heading IDs on if you want anchors. Line breaks on only if the source was written for a chat-style renderer. Allow raw HTML on only if the source is yours. Full document on if you want a complete HTML5 page with doctype, charset, viewport and a <title> taken from your first <h1> — useful when you want to open the result directly in a browser or drop it on a static host.
Step 3: Check the preview
Switch to the preview tab and confirm the structure. The stats row tells you words, headings, links, images, code blocks and reading time — handy for checking a post is the length you thought it was before you publish it. For a more thorough count, the Word Counter does readability and keyword density on the same text.
Step 4: Take the output
Copy the HTML, download it as an .html file, or copy the generated table of contents — a Markdown nested list linking to each heading anchor, ready to paste back at the top of your document.
If you are pasting the result into a page where bytes matter, run it through the HTML Minifier afterwards. The converter's output is indented for readability, not for the wire.
Common Use Cases
Getting a README onto a website
Plugin and package authors write a good README, then need the same content on a landing page. The README is GFM with tables and badges; the landing page needs HTML. Convert, paste, style with your existing CSS. The heading IDs give you a sidebar TOC for free.
Publishing to a CMS that only accepts HTML
Plenty of CMS fields, email platforms and legacy admin panels take HTML and nothing else. If you draft in Markdown — and most people who write regularly do — this is the bridge. Convert with full document off, so you get the fragment rather than a whole page, and paste it into the field.
Prototyping a docs page
Before you commit content into a docs site, converting it locally shows you the actual heading hierarchy and whether your code fences carry the right language. An h3 that should have been an h2 is obvious in the TOC and invisible in the source.
Auditing content someone else wrote
Paste a contributor's Markdown, look at the emitted HTML, and you can see immediately whether they used real headings or bolded a line to fake one — a habit that destroys document structure and accessibility. Screen readers navigate by heading; **Big Text** is not a heading, it is a paragraph in bold, and the converter shows you that in one line of output.
Extracting a table of contents
Long docs need one, and maintaining it by hand guarantees it goes stale. Generate it from the headings, paste it in, regenerate whenever the headings change.
Advanced: what this parser does and does not do
It is a hand-written parser, roughly 400 lines, with no dependencies — which is deliberate, because a Markdown parser that pulls a 200KB dependency into a page whose whole point is being fast is a bad trade.
Covered: ATX headings (# x) and setext headings (underlined with === / ---), paragraphs, emphasis and strong (*, _, **, __), inline code with backtick-run matching, fenced code with info strings, indented code blocks, blockquotes with lazy continuation, nested lists (ordered and unordered, tight and loose), thematic breaks, links and images with titles, angle-bracket autolinks, email autolinks, backslash escapes, and the GFM set: tables with alignment, task lists, strikethrough, bare-URL autolinking.
Not covered: reference-style links ([text][ref] with a [ref]: url definition elsewhere), footnotes, definition lists, and a few genuinely obscure CommonMark corner cases around HTML blocks interrupting paragraphs. If you are running the CommonMark conformance suite against it, it will not score 100%. If you are converting a README, a changelog or a blog post, you will not notice.
That is an honest trade, and it is the reason the converter loads instantly and works with the network off. For content pipelines where you need bit-exact CommonMark conformance, use markdown-it, remark or cmark in your build; that is what they are for.
FAQ
How do I convert Markdown to HTML?
Paste your Markdown into the editor and the HTML appears immediately — there is no convert button and no file to upload. Turn on GitHub Flavored Markdown if your source uses tables or task lists, then copy the HTML or download it as an .html file. Everything runs in your browser, so unpublished drafts and internal docs never leave your device.
What is GitHub Flavored Markdown?
GitHub Flavored Markdown (GFM) is a formally specified superset of CommonMark that adds tables, task list checkboxes, strikethrough with double tildes, and automatic linking of bare URLs. It is the dialect GitHub uses to render README files and issues, and it is what most Markdown editors emit today. It is enabled by default in this converter.
Why did my single line break disappear?
Standard Markdown joins consecutive lines into a single paragraph; a line break only survives if you end the line with two spaces, use a trailing backslash, or leave a blank line. If you want every newline to become a <br />, enable the line breaks option — that is the behaviour GitHub comments and most chat apps use, but it is not what README files do.
Does the converter highlight my code?
It emits the markup a highlighter needs but does not colour the code itself. A fenced code block tagged with the language js becomes <pre><code class="language-js">, which is the class convention Prism, highlight.js and Shiki all look for. Add one of those libraries to the page where you paste the output and the highlighting appears automatically.
Is raw HTML inside my Markdown preserved?
By default it is escaped, so <div> appears as literal text rather than a tag. Turn on the allow-raw-HTML option to pass tags straight through, which is what you want when your Markdown deliberately mixes in HTML — a <details> block, or an image with attributes. Only enable it for source you trust, because raw HTML from an untrusted author is an XSS vector.
Is it safe to paste Markdown I did not write?
Yes. HTML is escaped by default, and the live preview additionally strips script and style tags, inline event handlers and javascript: URLs before rendering. Nothing you paste is transmitted anywhere. If you are building a product that renders Markdown from strangers, still use a dedicated sanitiser such as DOMPurify server-side — a preview filter is not a substitute for one.
Can I generate a table of contents from my headings?
Yes. With heading IDs enabled, every heading gets a slugified, de-duplicated anchor, and the tool builds a Markdown table of contents linking to each one. Copy it back into the top of your document and the links resolve against the generated ids. Regenerate it whenever your headings change rather than maintaining it by hand.
Does this converter fully implement CommonMark?
It implements the constructs people actually write — ATX and setext headings, paragraphs, emphasis, links, images, autolinks, blockquotes, nested and loose lists, fenced and indented code, thematic breaks, backslash escapes — plus the GFM extensions. Reference-style links, footnotes and a few rare CommonMark HTML-block edge cases are not covered. For bit-exact conformance in a build pipeline, use markdown-it, remark or cmark.
Related tools: Markdown to HTML · HTML Entities · HTML Minifier · Word Counter · Slug Generator
Related reading: The Web Developer's Toolkit · Text Tools Guide
