Back to Blog
Developer Tools7 min read·September 11, 2026

Markdown Complete Guide - Syntax, Examples & Best Practices 2026

Learn Markdown syntax from basics to advanced. Complete guide with examples for headings, lists, code blocks, tables, links and more. Write better docs faster.

What Markdown Is and Which Version You Are Actually Writing

Markdown is a lightweight markup language created by John Gruber in 2004, with input from Aaron Swartz. The design goal: a Markdown file should read cleanly as plain text before anything renders it. Compare the same fragment in HTML and Markdown:

Release Notes

This build fixes a critical parser bug.

    • Faster startup
    • Smaller bundle

## Release Notes

This build fixes a critical parser bug.

  • Faster startup
  • Smaller bundle

"Markdown" is not one language. Gruber's original description, frozen at version 1.0.1 in December 2004, left many cases undefined, so implementations diverged. Three flavours matter in practice:

  • CommonMark — a strict specification with a machine-readable test suite of several hundred examples. The current release is 0.31.2, published in January 2024, and it is the base layer for most modern parsers.
  • GitHub Flavored Markdown (GFM) — CommonMark plus five specified extensions: tables, task lists, strikethrough, autolinks, and a filter for disallowed raw HTML. Footnotes deserve a caveat: github.com renders [^1], but footnotes are a site feature outside the published GFM specification, so a local CommonMark parser will not produce them.
  • Platform subsets — Slack, Discord, and Teams take a handful of inline markers and ignore the rest. Slack's mrkdwn has bold, italic, strikethrough, code, and blockquotes, but no heading syntax and no tables.

Decide which target renders a document before writing it: a table works on GitHub and fails on Slack. Paste a draft into a Markdown Editor to see what your syntax actually produces instead of guessing.

Block Syntax: Headings, Paragraphs, and Lists

Block elements are separated by blank lines. That rule causes more broken Markdown than anything else.

## Major Section

Subsection

#### Detail

A paragraph is any run of lines with no blank line between them.

This second line joins the same paragraph.

This is a new paragraph because a blank line came first.

Always put a space after the hash. CommonMark requires it, so #Heading renders as literal text, while Gruber's original Perl script accepted it — a portability trap worth knowing. Use one top-level heading per document and never skip levels; screen readers and search crawlers both read heading order as structure, so a jump from H2 straight to H4 is a real defect.

For lists, pick one bullet marker and keep it. Mixing - and * at the same level starts a brand new list in CommonMark:

- First item

  • Second item
- Nested item, indented two spaces

- Another nested item

  • Third item
  • Ordered items use numbers
  • The numbers you type do not matter
  • Renderers renumber sequentially
    • [ ] Unchecked task (GFM only)
    • [x] Completed task

    Nesting depends on indentation matching the parent's content column, not on a fixed tab width: after - that column is two, after 1. it is three. Get it wrong and the sub-list renders as a sibling. Because renderers renumber ordered lists anyway, many teams write 1. on every line, so reordering never touches the numbers.

    Inline Syntax and the Emphasis Rules That Trip People Up

    bold  *italic*  *bold italic*

    ~~strikethrough~~ (GFM)

    inline code

    Prefer bold and _italic_ over __bold__ and *italic*. CommonMark's emphasis rules refuse to open or close underscore emphasis inside a word, so snake_case_name survives, while * inside a word can open emphasis. That is why un*frigging*believable italicises but un_frigging_believable does not.

    For code containing backticks, widen the fence. A code span opened by a run of *n* backticks closes at the next run of exactly *n*, and one leading and trailing space is stripped so the content can itself start with a backtick:

    Use ` code  to show a literal backtick pair.

    Markdown has no line-break syntax that reads well. Two or more trailing spaces produce a
    but are invisible in review; a trailing backslash does the same in CommonMark and is at least visible. Prefer a real paragraph break to either.

    Links, Images, and Reference Style

    Inline link

    Link with title

    !Alt text describing the image

    Reference style keeps prose readable:

    See the [specification][spec] and the [test suite][tests].

    [spec]: https://spec.commonmark.org/

    [tests]: https://spec.commonmark.org/0.31.2/spec.json

    Reference definitions can sit anywhere outside a code fence and are not rendered. Labels are case-insensitive and whitespace-normalised, and [spec][] reuses the link text as the label.

    Alt text is not optional. It is the accessible name of the image and the main textual signal a crawler has for it, so ![](chart.png) wastes both. Describe the content, not the file. And remember that relative links resolve against the rendering location, which differs between a repository view and a published docs site: keep file links repository-relative and live-site links absolute.

    Code Blocks, Tables, and Blockquotes

    Always tag fenced blocks with a language. The tag drives syntax highlighting and, on GitHub, language statistics.

    python

    def slugify(title: str) -> str:

    return "-".join(title.lower().split())

    Indented code blocks (four spaces) still work, but inside a list those four spaces may instead mean "continue the item". Fences are the modern default.

    GFM tables need a header row and a delimiter row with the same number of cells; if the counts differ, the block is not a table at all and renders as a paragraph. Colons in the delimiter set alignment:

    FlavourTablesFootnotesRaw HTML

    CommonMarkNoNoYes
    GFMYesSite-onlyFiltered
    SlackNoNoNo

    Which renders as:

    FlavourTablesFootnotesRaw HTML

    CommonMarkNoNoYes
    GFMYesSite-onlyFiltered
    SlackNoNoNo

    Source columns do not need to line up — only the pipe count matters. Pipes inside a cell must be escaped as \|, and a table cell cannot contain a fenced code block, only inline code.

    Blockquotes nest, and can hold other blocks:

    > The quoted claim.

    >

    > - A bullet inside the quote

    >

    > > A nested quote.

    Converting Between Markdown and Other Formats

    Markdown is a source format, not a delivery format, so conversion is routine. Markdown to HTML is your static site generator's job, at build time. The other two directions need a tool: use HTML to Markdown when migrating a CMS or capturing a page, rather than stripping tags by hand, since nested lists and tables are where naive regex conversions fall apart; use Markdown to PDF for anything that must be signed or archived, since it paginates the rendered document.

    Round-tripping is lossy in one direction. Markdown to HTML is well defined; HTML to Markdown is not, because HTML expresses things Markdown cannot — colspans, inline styles, arbitrary attributes. Expect a converter to leave raw HTML in the output there, and treat that as correct rather than a bug.

    Rendering Markdown Safely

    Markdown is not a safe input format. CommonMark explicitly allows raw HTML blocks to pass straight through, so a comment field that accepts Markdown and renders the parser's output accepts HTML too:

    Looks harmless.

    Concrete rules for handling untrusted Markdown in 2026:

  • Never trust the parser to sanitise. marked deprecated its built-in sanitize option in 0.7.0 and removed it in v5, and documents that sanitising is the caller's job. markdown-it disables HTML by default (html: false) — leave it off for user content.
  • Sanitise the rendered HTML, not the Markdown source, using a maintained sanitiser such as DOMPurify with an allow-list. Filtering the source is bypassable because Markdown has many ways to reach the same HTML.
  • Sanitise link URLs too. click) is valid Markdown. Allow http, https, and mailto only.
  • Add rel="noopener noreferrer" to external links you render with target="_blank".
  • Note how narrow the GFM extension is: its disallowed-raw-html filter escapes nine named tags — script, iframe, style and textarea among them — and nothing else, so the onerror above survives untouched. READMEs are safe on github.com because the site runs a full HTML sanitiser with an attribute allow-list on top of the parser. That layer is the one you build yourself.

    A Practical Style Guide

    These conventions cost nothing and prevent most diff noise and rendering surprises.

    • One sentence per line, or hard-wrap at a fixed column — but never reflow a paragraph in the same commit as a content change; the diff becomes unreviewable.
    • Blank line before and after every heading, list, table, and fenced block. Missing blank lines are the top cause of "why is my list rendering as a paragraph".
    • Set up a linter. markdownlint catches inconsistent bullets, missing language tags, skipped heading levels, and trailing whitespace, and runs in CI against the same rules as your editor plugin.
    • Keep front matter valid. Static site generators read a YAML block delimited by ---` at the very top of the file; one bad indent breaks the build. Validate it with a YAML to JSON converter before committing.
    • Check length against your target. Meta descriptions, README summaries, and changelog entries usually have a budget — a Word Counter settles it faster than eyeballing.
    • Review rendered output, not the raw diff, whenever a change touches tables or nested lists. A source diff will happily show a clean one-character edit that silently collapses a whole table.
    #markdown guide#markdown syntax#markdown tutorial#markdown editor online

    Try Our Free Online Tools

    100+ free tools for developers, designers, and everyone. No sign-up required.

    Browse All Tools