Markdown Linter

Technical writers maintaining a docs site, developers polishing a README, and marketing teams drafting blog posts in markdown all benefit from a second pass before publish. This linter catches heading skips, bare URLs, empty links, missing image alt text, unclosed code fences, trailing whitespace, and inconsistent list markers. It runs entirely in the browser, so unreleased drafts and internal runbooks never leave your machine.

How the Linter Inspects Your Markdown

The linter splits the input into lines and tracks a fenced-code state machine so rules skip content inside triple-backtick blocks. Outside code fences it matches ATX headings, setext underline headings, inline links and images, and list markers. Each line is checked against the full rule set in a single pass, which keeps the linter fast even on multi-thousand-line documents.

Heading levels are collected into an ordered list, then checked for skips and for multiple H1s. Link destinations are tested for emptiness, and link text is compared against a set of generic phrases. Bare URLs are detected only when they are not already inside a markdown link, so a properly formatted link never triggers a false warning.

Errors vs. warnings

Errors break rendering or accessibility: heading level skips, empty link destinations, missing image alt text, and unclosed code fences. Warnings cover style and readability: trailing whitespace, bare URLs, generic link text, inconsistent list markers, and missing language tags on code blocks. Fix errors before publish; batch warnings into a cleanup pass.

Accessibility signals

Images without alt text and links with empty or generic text are the two most common accessibility failures in markdown-hosted docs. The linter flags both so your README or docs site passes automated screen reader checks without a separate audit pass.

Before and after: cleaning a README

Before (2 errors, 4 warnings)
# My Project
## Install
Run npm install.   
[Click here](https://example.com/docs)
![](screenshot.png)
### API
\`\`\`
no language tag
\`\`\`
After (0 errors, 0 warnings)
# My Project

## Install

Run npm install.

[Documentation](https://example.com/docs)

![Screenshot of the dashboard](screenshot.png)

### API

\`\`\`bash
npm install my-package
\`\`\`

Use Cases

Developer Production Workflow
Lint a README before tagging a release Check docs-site pages before a content freeze Pre-commit hook equivalent without npm install
Catch heading skips in a contribution guide Audit alt text on product screenshots in docs Review a guest post draft before scheduling
Verify code fence language tags for syntax highlighting Standardize list markers across a multi-author wiki Quick check on a Stack Overflow answer draft

Common Mistakes and Troubleshooting

A common false positive source is indented code blocks that use four spaces instead of triple backticks. The linter treats indented blocks as regular text, so trailing-whitespace and bare-URL rules may fire inside them. Convert indented code blocks to fenced blocks with a language tag to silence those warnings and gain syntax highlighting.

If a heading-skip error points at a line that looks correct, check the previous heading. Jumping from an h2 to an h4 in the source produces the error even though both headings render fine visually. Screen reader users rely on the numeric sequence, so fill the gap with an h3 or promote the later heading.

Best Practices for Markdown Hygiene

Run the linter on every markdown file before committing. Add language tags to all fenced code blocks so GitHub and static site generators apply syntax highlighting. Write descriptive link text instead of click here, and add alt text to every screenshot or diagram. Keep one H1 per file, start sub-sections at H2, and prefer a single blank line between blocks for consistent rendering across renderers.

For teams, adopt a shared rule set and run this linter in CI alongside a markdown renderer preview. Pair it with a readability score checker for prose quality and a markdown-to-text converter for plain-text exports.

Markdown Linter FAQ

What markdown rules does this linter check?

The linter runs checks for heading level skips, multiple H1s, duplicate headings, empty link destinations, missing image alt text, generic link text like click here, bare URLs, trailing whitespace, tab characters, unclosed code fences, code blocks without a language tag, inconsistent list markers, missing blank lines around headings, and multiple consecutive blank lines. Each finding includes the line number and a plain-language explanation.

How is this different from markdownlint or remark-lint?

markdownlint and remark-lint are excellent CLI and editor tools, but they require npm install and a config file. This linter runs in the browser with zero setup: paste your markdown, read the findings, fix, repeat. It covers the highest-impact rules that affect rendering and accessibility, so it works well as a quick pre-commit check or for non-technical writers who do not have a Node toolchain.

Does the linter validate markdown syntax or just style?

Both. Structural errors like unclosed code fences, empty link destinations, and heading level skips are flagged as errors because they break rendering or accessibility. Style issues like trailing whitespace, bare URLs, and inconsistent list markers are warnings. Fix every error; treat warnings as improvements that raise readability and SEO quality.

Why does the linter flag bare URLs?

A bare URL like https://example.com renders as plain text and gives screen readers nothing useful to announce. Wrapping it in a link, such as [Example](https://example.com), produces a proper anchor that assistive technology can identify and that passes link-text accessibility checks. The linter also flags generic anchor text like click here for the same reason.

Can I set a maximum line length?

The linter accepts an optional line-length option. When set, any line longer than the limit triggers a warning. Leave it at zero (the default) to skip length checks, which is common for README files where long lines are normal. For prose-heavy docs, a limit of 100 or 120 characters keeps paragraphs readable in side-by-side diffs.

Does it work on GitHub Flavored Markdown?

Yes. The linter understands fenced code blocks, setext and ATX headings, inline links and images, and the standard list marker syntax that GitHub Flavored Markdown uses. Tables and strikethrough are not checked for style, but they do not trigger false findings either.

Is my markdown uploaded to a server?

No. The entire lint pass runs in the browser tab. Your draft, unreleased documentation, and internal wikis stay on your machine. There is no account, no history, and no network request in the linting path.