Markdown is forgiving. Almost anything you type will render into something — which is exactly the problem. A skipped heading level, an image with no alt text, or a URL you forgot to wrap in link syntax will all render “fine” in your editor’s preview, then quietly cause real problems once the document ships: broken screen-reader navigation, a table of contents that doesn’t nest correctly, a link that looks wrong on a different platform.

None of these are syntax errors. Markdown doesn’t have a compiler that stops you. That’s exactly why linting matters — and why we built a Markdown Linter that checks for these issues in the browser, no setup required.

This post walks through the mistakes that actually show up in real documents, grouped by why they matter, with a before/after for each.

Why Markdown Style Mistakes Matter

Unlike a programming language, Markdown failures are usually silent. Three categories of consequence show up over and over:

  1. Accessibility — screen readers rely on a logical heading structure and on alt text to describe images. Get either wrong, and you’ve made the document harder to use for anyone relying on assistive technology.
  2. Structure and navigation — table-of-contents generators, doc-site sidebars, and “on this page” widgets all build their outline from your headings. A skipped level (H2 straight to H4) breaks that outline even though the page still looks normal.
  3. Cross-renderer consistency — a bare URL, a code block missing its language tag, or trailing whitespace can render differently — or not at all — depending on whether the renderer is GitHub, a static site generator, or an internal wiki.

None of these show up as a red squiggly line while you’re typing. You only find out when the rendered page looks wrong, or an accessibility audit flags it, or someone tells you the sidebar navigation skipped a section.

The 10 Rules, With Before/After Examples

Our Markdown Linter checks for 10 specific issues, split into one hard error and nine warnings. Here’s what each one actually catches.

1. Heading hierarchy (error)

The only rule in the tool that’s treated as an error rather than a warning, because it’s the one most likely to break something structural, not just look untidy.

Headings should step down one level at a time. You can jump back up freely (H3 back to H2 is fine — that’s just starting a new section), but you should never skip a level going down.

<!-- Bad: skips H2 entirely -->
# Getting Started
### Installation
<!-- Good -->
# Getting Started
## Installation

Why it matters beyond looks: screen readers let users jump between headings by level, and they announce the jump (“entering level 3 heading”) — a skipped level is confusing out of context. Search engines and TOC generators use the same hierarchy to build page outlines, so a skip can produce a lopsided or broken outline even though the text itself reads fine.

2. Multiple H1s

A document should have exactly one H1 — the page title. A second H1 partway through usually means someone meant to use H2.

<!-- Bad -->
# My Guide
Some intro text.
# Advanced Topics
<!-- Good -->
# My Guide
Some intro text.
## Advanced Topics

3. Duplicate headings

The same heading text appearing twice in one document — often from copy-pasting a section as a template for a new one and forgetting to rename it.

<!-- Bad -->
## Configuration
...
## Configuration

This matters for anchor links specifically: most renderers generate a heading’s URL fragment (#configuration) from its text, so the second “Configuration” heading either silently gets a suffixed slug (#configuration-1) that nobody linked to on purpose, or overwrites the first one’s anchor depending on the renderer — either way, a link meant for the first section can land on the wrong one.

4. No language on fenced code blocks

A ` ``` ` fence with no language identifier after it loses syntax highlighting and, on some platforms, loses the copy-button / line-numbering features that are tied to language detection.

<!-- Bad -->
```
function greet(name) {
  return `Hello, ${name}`;
}
```
<!-- Good -->
```javascript
function greet(name) {
  return `Hello, ${name}`;
}
```

5. Bare URLs

A plain URL sitting in text, not wrapped in [text](url) link syntax.

<!-- Bad -->
See https://example.com/docs/api-reference for details.
<!-- Good -->
See the [API reference](https://example.com/docs/api-reference) for details.

Some renderers auto-linkify bare URLs (GitHub does); others don’t, and you end up with dead-looking plain text. Even where auto-linking works, a long raw URL is worse for readability and worse for screen readers, which read out the entire URL character by character instead of a meaningful label.

6. Missing alt text

An image reference with empty alt text — ![]() instead of ![description](...).

<!-- Bad -->
![](chart.png)
<!-- Good -->
![Bar chart showing monthly signups from January to June](chart.png)

This is the single highest-impact accessibility fix on this list. Screen reader users get nothing at all from an image with empty alt text — not even a filename fallback in most cases. It’s also one of the easiest mistakes to make, since a missing alt attribute doesn’t change how the image looks to a sighted reader.

7. Hard tabs for indentation

A line indented with a literal tab character instead of spaces.

Hard tabs render inconsistently — different editors and renderers expand a tab to a different number of spaces (commonly 4 or 8), so indentation-sensitive content like nested lists can shift alignment depending on where it’s viewed. Most Markdown style guides (including markdownlint’s defaults) require spaces for exactly this reason.

8. Trailing whitespace

Whitespace at the end of a line. This is a genuinely dangerous one because Markdown gives exactly two trailing spaces special meaning: it forces a line break within a paragraph. One trailing space (or three, or an odd number) does nothing visible in most editors, so it silently changes rendering behavior in a way you won’t notice until you inspect the raw output. (The linter is deliberately careful here — it only flags true trailing whitespace, not the intentional two-space line-break convention.)

9. Multiple consecutive blank lines

More than one blank line between blocks. Markdown collapses any run of blank lines down to a single paragraph break during rendering, so extra blank lines have zero visual effect — they’re pure noise in the source that makes diffs longer and documents harder to scan in an editor.

10. Line length

A prose line longer than 120 characters. Code blocks are excluded from this check, since code often has legitimate reasons to run long (and it’s usually monospaced and horizontally scrollable in rendered output anyway).

Long prose lines aren’t a rendering problem — Markdown reflows paragraphs regardless of source line length — but they’re a diff problem. In a docs-as-code workflow where changes are reviewed as Git diffs, a one-word edit to a 400-character line shows the entire line as changed, making the actual edit hard to spot in review.

In-Browser Linting vs. CI Pipeline Linting

If you’ve read our docs-as-code workflow guide, you already know about markdownlint and Vale — the standard tools for enforcing Markdown style in CI. Those are the right tools for a real docs pipeline: they run on every pull request, they’re configurable per-project, and Vale in particular goes beyond structure into prose style (banned phrases, terminology consistency, passive voice).

Our Markdown Linter isn’t a replacement for that setup — it’s the fast check before you get anywhere near a pull request. Paste a document, get instant feedback, no npm install, no config file, no CI run to wait on. It’s useful in a few specific situations a CI pipeline doesn’t cover well:

  • Reviewing someone else’s Markdown you don’t have local tooling set up for — paste it and check in seconds
  • Before pasting into a CMS or wiki that doesn’t run any linting at all, where structural issues silently affect SEO and accessibility with no safety net
  • Quick self-review while drafting, before you’ve even opened a terminal
  • Teaching or onboarding — showing someone exactly what “broken heading hierarchy” looks like, with immediate before/after feedback, rather than pointing them at a CI log

Think of it as the same relationship a linter extension in your editor has to your project’s full CI test suite: faster feedback, narrower scope, and no substitute for the real pipeline once the stakes are high enough to need one.

Try It

Paste your Markdown into the Markdown Linter and you’ll get line-numbered results, color-coded by severity, updating as you type. No signup, nothing leaves your browser.