← BlogGuides

Clean Markdown: 9 Rules for Readable, Lint-Free Docs

BMBest Markdown Team8 min readGuides
A raw Markdown file transforming into a clean, structured document, surrounded by icons for a Git diff, a terminal, and an AI chat — the three readers of clean Markdown.

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).
Side-by-side comparison of raw Markdown source: the left pane is messy with skipped heading levels, mixed bullet characters, and an unlabelled code fence; the right pane is clean with consistent structure, both rendering to the same visual output below.
Same rendered output, two very different source files. Clean Markdown wins where the source is read: diffs, terminals, and LLM context.

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.

A diagram showing a correct heading tree (H1 to H2 to H3 nesting cleanly) next to an incorrect one where a level is skipped from H2 to H4, with the broken branch highlighted in red.
Heading level is structure, not size. Skipping a level breaks tables of contents, accessibility, and linters.

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:

DecisionPick oneAvoid mixing with
Bullets-* or +
Bold**bold**__bold__
Italic_italic_*italic*
HeadingsATX (##)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-commitmarkdownlint-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.

A code editor showing a Markdown file with markdownlint warnings underlined: a skipped heading level, a code fence missing its language, and a list without a surrounding blank line, each with its rule code like MD001 and MD040.
markdownlint flags every rule in this post automatically — MD001 for skipped headings, MD040 for unlabelled fences, MD022 for missing blank lines.

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/intro or ./img.png that 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.

A flow diagram: a messy web page with navigation and ads on the left, an arrow through a converter that strips chrome and labels code, producing a clean Markdown file on the right, then an arrow through a linter producing a verified-clean file.
Most messy Markdown is produced, not typed. Clean source plus a lint pass beats hand-cleaning every time.

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.

Share
BM

Best Markdown Team

We build free, privacy-first tools for writing and converting Markdown. Made for developers, by developers.