Most Markdown advice tells you the syntax. Almost none of it tells you what
separates Markdown that reads well from Markdown that technically renders.
Here's the thing most guides miss: the cleanest Markdown is rarely typed by
hand — it's converted or generated, and that's exactly where it gets messy.
We build an HTML-to-Markdown converter, so we see broken output all day: skipped
heading levels, code fences with no language, raw <div>s left inline. These are
the 9 rules that keep Markdown readable in raw text, clean in diffs, and parseable
by both humans and LLMs — plus the linter that enforces them for free.
Why clean Markdown matters (even though it "still renders")
Markdown is forgiving. You can skip heading levels, mix - and * bullets, and
leave code fences unlabelled, and most renderers will still show something. So
why bother? Because Markdown's whole point is that the source is the document,
and three readers care about the source:
- Humans reading the raw file — in a terminal, a PR diff, a plain-text editor. Clean Markdown is legible with the preview turned off. That's the original design goal: John Gruber's spec says a Markdown document should be "publishable as-is, as plain text."
- Version control — Markdown lives in Git for a reason. Messy source produces noisy diffs where a one-word fix reflows an entire paragraph, burying the real change.
- Machines — static-site generators, linters, and increasingly LLMs. Clean structural Markdown is one of the most token-efficient, unambiguous formats you can feed a model (we cover that in Markdown for ChatGPT & Claude).

The payoff is consistency: when every file in a repo follows the same rules, nobody wastes a review cycle on style, and tooling can transform the docs without surprises. Now the rules.
Headings & document structure (rules 1–2)
Rule 1 — One H1, and never skip heading levels. A document has exactly one
top-level title (#). Everything below nests in order: # → ## → ###. Don't
jump from ## straight to #### because you like the smaller text — heading
level is structure, not font size. Screen readers, tables of contents, and
linters all rely on that hierarchy, and a skipped level breaks all three.
# Page title ← exactly one H1
## Section ← then H2
### Subsection ← then H3, no skipping
Rule 2 — Use ATX headings and surround block elements with blank lines. Use
the # style (called ATX), not the underline style (===/---, called Setext) —
it's explicit at every level and easier to grep. And put a blank line before and
after every heading, list, code block, blockquote, and table. This is the single
most common cause of "why won't my list render?" — a list jammed against the
paragraph above it gets swallowed into that paragraph by many parsers.
The blank-line rule, stated once: a block element (heading, list, fence, table, blockquote) needs an empty line on both sides. CommonMark is stricter about this than GitHub's renderer, so code that "works on GitHub" can break elsewhere. Add the blank lines and it works everywhere.

Emphasis, links & line length (rules 3–4)
Rule 3 — Pick one style and commit to it. Markdown gives you choices, and inconsistency is what makes a repo feel sloppy. Decide once, document it, and enforce it with a linter:
| Decision | Pick one | Avoid mixing with |
|---|---|---|
| Bullets | - | * or + |
| Bold | **bold** | __bold__ |
| Italic | _italic_ | *italic* |
| Headings | ATX (##) | Setext (---) |
There's no "correct" choice here — - for bullets and ** for bold are the most
common conventions and what most formatters default to — but consistency within
a project is the rule that matters.
Rule 4 — Write one sentence per line, and write real link text. Markdown collapses consecutive non-blank lines into one paragraph when it renders, so a line break in your source doesn't add a line break in the output. Use that: put each sentence (or each independent clause) on its own line. The rendered paragraph looks identical, but now editing one sentence produces a one-line diff instead of a reflowed wall of red and green. This convention is called semantic line breaks.
For links, never use bare URLs or "click here". Write descriptive anchor
text — see the [installation guide](#) — because the link text is what screen
readers announce and what scanning readers see. And when your Markdown might
travel (converted from a web page, pasted into another repo), make URLs
absolute so /docs/intro doesn't 404 the moment it leaves the original site.
Lists & task lists (rule 5)
Rule 5 — Keep list markers and indentation consistent. A few habits keep lists from breaking:
- Use a single bullet character (
-) for the whole document. - Indent nested items by the width of the parent marker — two spaces for
-lists. Tabs and ragged indentation are the usual reason a nested list flattens or a sub-item escapes its parent. - For ordered lists, you can number every item
1.and let the renderer count — it keeps diffs clean when you reorder — but many teams prefer real numbers for raw readability. Pick one (see Rule 3). - Task lists use
- [ ]and- [x]. They're GitHub-Flavored Markdown, not part of the original spec, so they render as checkboxes on GitHub but may show as literal brackets on a plain CommonMark renderer.
- Parent item
- Nested item (two-space indent under a `-`)
- [ ] Unchecked task
- [x] Completed task
Code blocks & blockquotes (rule 6)
Rule 6 — Always label your code fences. This is the rule converters and AI output break most often. A fenced block with no language:
```
const x = 1;
```
…renders as grey, unhighlighted text. Add the language and it gets syntax highlighting everywhere it's displayed:
```js
const x = 1;
```
Use fenced code blocks (triple backticks), not indented ones — fences are
explicit, support a language hint, and won't accidentally absorb a paragraph that
happened to be indented. For blockquotes, keep the > on every line including
the blank lines inside the quote, and use them for actual quotations or callouts
— not as a styling hack to indent text.
Tables that stay aligned (rule 7)
Rule 7 — Keep tables simple and let a formatter align them. Markdown tables are GFM, and they're great for genuinely tabular data — but they get unreadable in source fast. Two guidelines:
- Don't hand-align the pipes. Padding columns with spaces by hand is busywork that breaks the moment you edit a cell. Write them loosely and let a formatter (Prettier, or your editor) align them on save.
- Keep cells short. Tables can't hold lists, multiple paragraphs, or block code. If a cell wants to be a paragraph, you don't want a table — use a definition-style list or headings instead.
| Method | Install? | Best for |
| --- | --- | --- |
| Online converter | No | One-off snippets |
| Pandoc | Yes | Batch conversion |
The alignment row (:---, :---:, ---:) controls left/center/right. If your
tables are getting painful to maintain by hand, that's usually the signal to reach
for a table generator or converter instead of editing raw pipes.
Rule 8 — Lint it automatically
Rule 8 — Stop enforcing style by hand; let a linter do it. Every rule above is mechanical, which means a tool can check it. The standard is markdownlint: it flags skipped heading levels (MD001), missing blank lines around blocks (MD022/MD031), inconsistent bullet characters (MD004), unlabelled code fences (MD040), and dozens more. Run it three ways:
- In your editor — the markdownlint VS Code extension underlines issues as you type.
- On save — pair it with Prettier, which auto-formats spacing, wraps, and table alignment so style is never a manual step.
- In CI / pre-commit —
markdownlint-cli2 "**/*.md"as a check fails the build on violations, so style stays consistent without anyone policing it.
Configure it once in a .markdownlint.json, commit that file, and your "style
guide" becomes executable instead of a wiki page nobody reads.

Common mistakes — and the one that causes most of them (rule 9)
The recurring offenders, in the order we see them:
- Skipped heading levels — jumping
##→####for visual size. Breaks TOCs and accessibility. (Rule 1.) - Missing blank lines — lists and code fences jammed against text, so they don't render. (Rule 2.)
- Unlabelled code fences — no syntax highlighting, harder to read. (Rule 6.)
- Raw HTML left inline —
<div>,<span>,<br>, and inline styles that survived a conversion. They bloat the file and don't render as Markdown. - Broken relative links and images —
/docs/introor./img.pngthat worked on the source site and 404 everywhere else.
Rule 9 — Mind where your Markdown comes from. Here's the pattern behind almost
every mess above: people don't type these mistakes — their tools generate them.
Paste a web page into a naive converter and you get the nav bar, the cookie
banner, unlabelled fences, and a pile of inline <div>s. Ask an LLM for a doc and
you get inconsistent heading levels and raw HTML. The fix isn't cleaning up by
hand every time — it's starting from a better source: a converter that
extracts clean main content and resolves links,
then a linter and formatter pass before you commit. Our
browser extension strips page chrome and fixes relative links during
the conversion, so the Markdown is clean before it ever hits your editor.

FAQ
What is clean Markdown?
Clean Markdown reads well as plain text, renders correctly everywhere, and stays
consistent: one H1 with no skipped heading levels, blank lines around block
elements, language-labelled code fences, and a single chosen style for bullets and
emphasis. The test — open the raw .md with no preview; if it's still easy to
read, it's clean.
What are the most important Markdown best practices? Use one H1 and never skip heading levels; put blank lines around headings, lists, code, and tables; always give code fences a language hint; write one sentence per line for clean diffs; and pick one style for bullets and emphasis. A linter enforces the rest.
Should I use one sentence per line in Markdown? For anything in version control, yes. Markdown joins consecutive lines into one rendered paragraph, so source line breaks don't change the output — but one sentence per line means a small edit is a one-line diff instead of a reflowed paragraph. It's the semantic line breaks convention.
What tool checks Markdown style? markdownlint is the standard — CLI, VS Code extension, and CI/pre-commit. Pair it with Prettier to auto-fix spacing and table alignment so style is never a manual argument.
Why is my converted or AI-generated Markdown messy? Because most messy Markdown is produced, not typed. Converters and LLMs emit inconsistent headings, unlabelled fences, inline HTML, and broken relative links. Start from a converter that extracts clean main content, then lint and format before committing.
Clean Markdown isn't about memorizing strict syntax — it's about source that's readable on its own, diffs that show the real change, and structure that machines can parse. Adopt the 9 rules, make markdownlint enforce them, and start from a clean conversion so you're editing good Markdown instead of fixing bad Markdown.
