Web Development
Markdown Syntax Guide: From Basics to Tables and TOCs
Markdown turns plain text into formatted docs. Learn the portable CommonMark core, GFM extras like tables and task lists, and the blank-line rule everyone forgets.
Markdown is a plain-text formatting syntax that converts to HTML. You write # Heading and get an <h1>; you wrap a word in ** and get bold. The portable core that every modern renderer agrees on is CommonMark, and the popular superset that GitHub, GitLab, and most static-site generators use is GitHub Flavored Markdown (GFM), which adds tables, task lists, strikethrough, and autolinks. The single rule that trips up nearly everyone: block-level elements need a blank line between them. A heading jammed against a paragraph, a list with no blank line before it, or a code fence touching the line above will often render as one mangled blob.
That blank-line rule is the source of most "why doesn't my Markdown render?" bugs, and it's the first thing to check before you blame the parser. The rest of this guide is the working reference — every construct shown as a real snippet inside a code fence so it renders as code, not as live formatting — plus the CommonMark-vs-GFM differences that decide whether your document survives being copied from GitHub into a different tool.
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. It is the lowest common denominator: if it works in CommonMark, it works almost everywhere.
- GFM — GitHub Flavored Markdown, specified at github.github.com/gfm, is CommonMark plus four extensions: 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.
TL;DR
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
Text Processing Tools Every Content Creator Needs in 2024
Streamline your content creation workflow with these powerful text processing tools. From word counters to readability analyzers, discover tools that save time and improve quality.
Read postUnix Timestamps and Epoch Time: The Complete Developer Guide
A Unix timestamp counts seconds since 1970-01-01T00:00:00 UTC, ignoring leap seconds. Learn seconds vs milliseconds, the Year 2038 problem, and conversion.
Read postpx vs em vs rem in CSS: Which Unit to Use and When
px is an absolute pixel, em is relative to the element's own font-size, rem is relative to the root. Default to rem for accessibility — here is why and when.
Read post