Web Development
Markdown Syntax Guide: Headings, Lists, Tables and Links
Learn Markdown through examples. Separate CommonMark syntax from table and task-list extensions, and check renderer differences before publishing.
Markdown expresses document structure in plain text, including headings, lists, links, and code blocks. CommonMark defines core syntax; extensions add features such as tables and task lists. This guide explains the distinctions and examples to check. Draft in the Markdown Editor, build tables with the Markdown Table Generator, and verify output in the destination renderer.
Blank lines often improve source readability, but the exact parsing rules depend on the block type. CommonMark permits headings and fenced code blocks without surrounding blank lines in several contexts. Lists, indentation, and paragraph interruption have their own rules; check the relevant construct rather than applying one rule to every block.
What is Markdown, and which "Markdown" are you using?
Markdown was created by John Gruber in 2004 as a way to write formatted text that stays readable as plain text. The original spec was loose and ambiguous, so implementations diverged for a decade. Two reference points now matter:
- CommonMark — the strict, unambiguous specification at spec.commonmark.org. It defines headings, emphasis, lists, links, images, blockquotes, code, and horizontal rules. Check whether the destination renderer follows CommonMark and which extensions it enables.
- GFM — GitHub Flavored Markdown, specified at github.github.com/gfm, is CommonMark plus extensions including: tables, task lists, strikethrough, and autolinking of bare URLs. GFM is a strict superset, so every CommonMark document is valid GFM, but not the reverse.
The practical takeaway: tables and task lists are GFM, not core Markdown. If you paste a GFM table into a renderer that only speaks CommonMark, you'll see raw pipes and dashes instead of a table. Know your target before you reach for an extension.
Headings, emphasis, and text basics
Headings use one to six # characters, and you should always put a space after the hash. Emphasis uses asterisks or underscores. Inline code uses single backticks.
# H1 — the page title (use exactly one per document)
## H2 — major section
### H3 — subsection
#### H4
##### H5
###### H6
*italic* or _italic_
**bold** or __bold__
***bold italic***
`inline code` for variable names, flags, and short literals
Put a blank line above and below every heading and paragraph.
A few rules people get wrong:
- One space after the
#.#Heading(no space) is not a heading in CommonMark — it's literal text. GFM is the same. *is more portable than_. Underscores inside words (my_variable_name) can trigger accidental emphasis in some parsers; backtick the identifier instead.- One H1 per document. Use a single top-level heading and descend in order. Skipping levels (H2 straight to H4) hurts accessibility and the generated table of contents.
Lists: ordered, unordered, and nested
Unordered lists use -, *, or +. Ordered lists use a number followed by a period. The actual numbers you type don't matter for rendering — 1. repeated still renders 1, 2, 3 — but writing real sequence numbers keeps the source readable.
- First item
- Second item
- Third item
1. Step one
2. Step two
3. Step three
- Top level
- Nested (indent with TWO spaces under a `-`)
- Deeper nesting (two more spaces)
- Back to top level
1. Ordered parent
1. Nested ordered (indent to align under the text, not the marker)
2. Second nested item
The nesting indentation is where lists break most often. The child must be indented far enough to align under the parent's content, not its marker. For a - item that's two spaces; for an ordered 1. item it's three. Critically, do not mix tabs and spaces for that indentation — a tab and "some spaces" can resolve to different column counts in different parsers, and your nested item silently flattens or escapes the list. Pick spaces and stay consistent.
Links and images
A link is text in square brackets followed by a URL in parentheses. An image is the same with a leading !, where the bracketed text becomes the alt attribute.
[Markdown editor](/tools/markdown-editor)
[Markdown editor](/tools/markdown-editor "Optional title shown on hover")

<!-- Reference-style links keep prose clean when reused -->
See the [CommonMark spec][cm] and the [GFM spec][gfm].
[cm]: https://spec.commonmark.org/
[gfm]: https://github.github.com/gfm/
Always write meaningful image alt text — it's the accessibility hook and an SEO signal, not decoration. Reference-style links (the [text][label] form with definitions collected at the bottom) are worth it when the same URL appears several times or when inline URLs would wreck the readability of your source.
Blockquotes
Blockquotes start each line with >. They nest with additional > characters and can contain other block elements.
> This is a blockquote.
> It can span multiple lines.
>
> It can contain multiple paragraphs if you keep the `>` on the blank line.
>
> > Nested blockquote for a reply or a quote-within-a-quote.
Note the > on the blank line between paragraphs — drop it and the quote ends early. This is the blank-line rule wearing a different hat.
Inline code and fenced code blocks
Use single backticks for inline code. For multi-line code, use a fenced code block: three backticks (or three tildes) on their own line, optionally followed by a language hint that triggers syntax highlighting.
Inline: run `npm install` then `npm run build`.
```js
// The word after the opening fence is the language hint
function greet(name) {
return `Hello, ${name}`;
}
```
```bash
git switch -c feature/new-table
```
~~~python
# Tildes work too — handy when your code itself contains backticks
print("clean fences")
~~~
Two practical notes. First, if your code sample contains triple backticks (for example, you're documenting Markdown itself, as this post does), wrap the outer fence in four backticks or use tildes — otherwise the inner fence closes the outer one early. Second, the language hint after the opening fence (js, bash, python, json, sql) is what enables highlighting; an unlabeled fence still renders as a code block but without color.
Horizontal rules
Three or more hyphens, asterisks, or underscores on their own line produce a horizontal rule (<hr>).
---
***
___
Keep a blank line above the ---. Without it, a line of text immediately followed by --- is interpreted as a Setext heading (the dashes underline the text into an H2) rather than a horizontal rule — a classic surprise.
Tables (GFM only)
Tables are a GFM extension — CommonMark core does not define tables at all, so a pure-CommonMark renderer will show your table as raw text. In GFM, columns are separated by pipes (|) and the header row is separated from the body by a row of dashes. Colons in that separator row set column alignment.
| Left aligned | Center aligned | Right aligned |
| :----------- | :------------: | ------------: |
| apples | 42 | $1.20 |
| oranges | 7 | $0.95 |
| pears | 128 | $3.40 |
The colon rules: :--- is left, :--: is center, ---: is right, and a bare --- defaults to left. The cell content doesn't need to be aligned in your source — pipes alone delimit columns — but lining it up keeps the raw Markdown legible. Tables also can't contain block elements (no lists or fenced code inside a cell); for anything richer you drop to HTML.
Hand-aligning pipes is tedious and error-prone past a few columns. The Markdown table generator builds correctly-aligned GFM tables from a grid (or pasted CSV) so the dashes and colons line up the first time. It runs entirely in your browser.
Task lists, strikethrough, and autolinks (GFM extensions)
Three more GFM additions you'll use constantly on GitHub:
<!-- Task lists: a list item whose marker is followed by [ ] or [x] -->
- [x] Write the draft
- [x] Add code examples
- [ ] Proofread
- [ ] Publish
<!-- Strikethrough: wrap in double tildes -->
~~This was wrong~~ and here is the correction.
<!-- Autolinks: GFM turns bare URLs into clickable links automatically -->
Visit https://commonmark.org/ — no brackets needed.
All three are defined in the GFM spec and none are in CommonMark core. Task lists in particular are a GitHub rendering feature: a checkbox in a .md file shows as a tick box in the GitHub UI and feeds issue/PR progress counters. In a plain CommonMark renderer, - [ ] is just a list item containing the literal text [ ].
Table of contents: how anchors are generated
A table of contents is a list of links that jump to headings on the same page. The links work because most renderers automatically assign each heading an id (an anchor / "slug") derived from its text. The common algorithm — used by GitHub and most static-site tooling — is:
- Lowercase the heading text.
- Strip out characters that aren't letters, numbers, spaces, or hyphens.
- Replace each run of spaces with a single hyphen.
- De-duplicate by appending
-1,-2, … to repeated slugs.
So ## Tables (GFM only) becomes the anchor tables-gfm-only, and you link to it with [Jump](#tables-gfm-only).
## Table of contents
- [Headings, emphasis, and text basics](#headings-emphasis-and-text-basics)
- [Lists: ordered, unordered, and nested](#lists-ordered-unordered-and-nested)
- [Tables (GFM only)](#tables-gfm-only)
- [Table of contents: how anchors are generated](#table-of-contents-how-anchors-are-generated)
The fiddly parts are punctuation and duplicate headings — get one character wrong and the link 404s to nowhere on the page. Rather than slugify by hand, paste your document into the Markdown TOC generator; it reads your headings, applies the GitHub slug rules (including duplicate de-duplication), and outputs a ready-to-paste linked list. Generate it last, after the headings are final, so the anchors don't drift.
CommonMark vs GFM: what's actually supported
When you can't get a construct to render, the answer is almost always that you're using a GFM extension in a CommonMark-only renderer. This is the dividing line:
| Feature | CommonMark core | GFM (superset) |
|---|---|---|
Headings (#) | Yes | Yes |
| Emphasis / bold | Yes | Yes |
| Ordered / unordered lists | Yes | Yes |
| Links & images | Yes | Yes |
| Blockquotes | Yes | Yes |
| Inline & fenced code | Yes | Yes |
| Horizontal rules | Yes | Yes |
| Raw inline HTML | Yes | Yes (sanitized on GitHub) |
| Tables | No | Yes |
| Task lists | No | Yes |
Strikethrough (~~) | No | Yes |
| Autolinks (bare URLs) | No | Yes |
If a document must travel between systems (a README on GitHub copied into a CMS, say), stick to the left column for the body and treat tables and task lists as "GitHub-only" decoration.
Common mistakes (and the fixes)
These four cause the overwhelming majority of broken Markdown:
- Missing blank lines between block elements. A list, heading, blockquote, or code fence pressed directly against the line above frequently merges into a paragraph. Fix: put one blank line above and below every block element. This single habit eliminates most rendering bugs.
- Mixing tabs and spaces in nested lists. Tabs and spaces resolve to different column widths across parsers, so a nested item escapes the list or flattens to the top level. Fix: indent nested items with spaces only — two spaces per level under
-, aligned under the parent's text. - Relying on raw HTML that gets stripped. You can embed HTML in Markdown, but renderers sanitize it. GitHub strips
<script>,<style>, inline event handlers, and moststyleattributes; many CMSs strip far more. Fix: use native Markdown where it exists, and don't depend on HTML that a sanitizer will remove. - Unescaped special characters. Literal
*,_,`,#,[, and|can be misread as formatting. Want a literal asterisk? Write\*. Want a literal pipe inside a table cell? Escape it as\|so it isn't treated as a column break. Fix: prefix the character with a backslash, or wrap it in inline code if it's a literal symbol.
Use a backslash to show a literal character: 1 \* 2 = 2 (not italic).
A literal pipe inside a table cell: `a \| b` stays in one column.
A leading number that should NOT start an ordered list: 1986\. The year.
Where to write, preview, and convert
Markdown is a writing format, so the fastest feedback loop is a live editor with a preview pane:
- Draft and see rendered output side by side in the Markdown editor — write on the left, watch the HTML render on the right, in your browser.
- Export finished Markdown to HTML for a CMS or email with the Markdown to HTML converter.
- Build aligned GFM tables without counting pipes using the Markdown table generator.
- Generate anchor-correct navigation with the Markdown TOC generator.
- Check length and reading time with the word counter, and fix heading capitalization fast with the case converter.
For the broader text-wrangling workflow these tools fit into, see text processing tools for content creators, and browse the full set under all tools.
Summary
Markdown is plain text that renders to HTML: learn the CommonMark core (headings, emphasis, lists, links, images, blockquotes, code, rules) once and it works everywhere, then add GFM extensions (tables, task lists, strikethrough, autolinks) when your target supports them. Tables and task lists are GFM, not core — the most common reason a snippet "doesn't render." Put a blank line between every block element, indent nested lists with spaces only, escape literal *, |, and `, and generate anchors and tables with a tool instead of by hand.
Keep the loop tight — write in the Markdown editor, align tables with the table generator, build navigation with the TOC generator, and ship the HTML from the converter — and Markdown stays what it was designed to be: readable as text, structured as HTML, portable everywhere.
Keep reading
Related posts
Words to Pages: Estimates for 500, 1000 and 2000 Words
Estimate pages from word count using explicit layout assumptions. Compare examples and learn when spacing, fonts, margins, tables, and images change the result.
Read postText Editing Workflow: Count, Clean and Format Content
Use text tools to count words, clean pasted content, compare revisions, and prepare Markdown. Preserve meaning and formatting through each editing step.
Read postSales Tax and VAT Calculations: Add and Extract Tax
Calculate tax from a net price or extract it from a total. Compare formulas, rounding, and jurisdiction-specific rules that a calculation cannot determine.
Read post