All posts
May 9, 20265 min read

Markdown to HTML: a practical reference

Markdown is a plain text format that converts to HTML. You write with light markup characters like #, *, and >, and a parser turns that into structured HTML elements. The appeal is that the source stays readable on its own, and the output is clean semantic markup. This reference covers the common syntax, how it maps to HTML, and where parsers disagree.

The core syntax that every parser supports

These constructs behave the same way across essentially every Markdown implementation. They come from the original spec and the CommonMark standard that formalized it.

Headings

A line that starts with one to six # characters becomes a heading. The number of hashes sets the level.

# Title
## Section
### Subsection

This produces <h1>Title</h1>, <h2>Section</h2>, and <h3>Subsection</h3>. Use one <h1> per document and do not skip levels if you care about accessibility and SEO.

Emphasis

Wrap text in single asterisks or underscores for italic, double for bold.

*italic* or _italic_      -> <em>italic</em>
**bold** or __bold__      -> <strong>bold</strong>

<em> and <strong> carry semantic weight, not just visual styling, which is why they are preferred over <i> and <b>.

Links and images

[label](https://example.com)        -> <a href="https://example.com">label</a>
![alt text](image.png)               -> <img src="image.png" alt="alt text">

Always write meaningful alt text. It becomes the alt attribute and matters for both accessibility and image search.

Lists

Unordered lists use -, *, or +. Ordered lists use a number followed by a period.

- one
- two

1. first
2. second

These map to <ul> and <ol> with <li> children. Indent items to nest them.

Code

Inline code uses single backticks: `code` becomes <code>code</code>. Fenced code blocks use three backticks, optionally with a language hint:

```js
const x = 1;
```

This produces a <pre><code class="language-js"> block. The language class is what syntax highlighters read.

Blockquotes

Prefix lines with >:

> quoted text

This becomes <blockquote><p>quoted text</p></blockquote>.

Where flavors differ

CommonMark is the precise, widely implemented baseline. GitHub Flavored Markdown, known as GFM, is a superset that adds features many writers expect but that plain CommonMark does not include. If your text renders differently in two places, a flavor difference is the usual cause.

Tables

Tables are a GFM extension, not part of CommonMark.

| Name | Type   |
| ---- | ------ |
| id   | string |

This renders as a full <table> with <thead> and <tbody>. The colons in the separator row control alignment: :--- for left, :--: for center, ---: for right. A strict CommonMark parser leaves this as literal text.

Task lists

Also GFM. A list item with a checkbox:

- [x] done
- [ ] not done

These become list items with disabled <input type="checkbox"> elements. Outside GFM they stay as plain text with visible brackets.

Strikethrough

GFM supports ~~deleted~~, which maps to <del>deleted</del>. CommonMark does not define this.

Autolinks

GFM automatically turns a bare URL like https://example.com into a link. CommonMark requires angle brackets: <https://example.com>. Without GFM, a bare URL stays plain text.

Raw HTML is usually allowed

Most parsers, including CommonMark and GFM, pass inline HTML through to the output. You can drop a <sub>, a <kbd>, or a <details> block straight into your Markdown and it renders. This is the escape hatch for anything Markdown cannot express.

Press <kbd>Ctrl</kbd> + <kbd>C</kbd> to copy.

Be careful with raw HTML when you accept Markdown from untrusted sources, because it can carry scripts. Renderers built for user content sanitize the output. For your own writing it is a convenient tool.

Preview as you write

The fastest way to catch a malformed table, an unclosed code fence, or a heading that did not register is to see the rendered HTML beside your source. The Markdown Previewer renders your text live in the browser as you type, so you confirm the output before you publish. Nothing is uploaded: the conversion runs locally on your machine, which matters when the document is a draft you are not ready to share.

When you move that output into a larger pipeline, a few other local tools pair well. If your front matter is YAML, the YAML to JSON converter checks that it parses. If a code block embeds JSON, the JSON formatter validates and tidies it. And if you are testing a find and replace pattern across your Markdown source, the regex tester lets you build the expression with live match highlighting first.

A short checklist

  • Stick to CommonMark syntax for portability, add GFM features only where the target supports them.
  • Write real alt text and keep one <h1> per page.
  • Tag fenced code blocks with a language for highlighting.
  • Use raw HTML sparingly, and sanitize it when the source is untrusted.
  • Preview the rendered HTML before you ship.