Markdown Headings
Markdown headings turn a wall of text into a scannable, navigable structure — the same # syntax works whether you're writing a README, a docs site, or a blog post that gets converted straight to HTML. This guide goes past basic # syntax into the parts most guides skip: custom anchor IDs, how different static site generators actually render headings, and how heading structure affects on-page SEO.
For the full syntax reference including compatibility across CommonMark and GFM, see Markdown Syntax. For a quick copy-paste version, use the Markdown Cheat Sheet.
💡 Practice as you read: Open the Markdown Editor for live preview.
What Are Markdown Headings?
A Markdown heading is created by prefixing text with one or more # characters — no font-size changes, no styling tags. Parsers convert # through ###### directly into HTML <h1>–<h6>.
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
Live Preview#
Two compatibility rules most guides skip:
- Always leave one space after the
#. Some parsers silently ignore##Headingwith no space — it just prints as plain text. - Leave a blank line before and after every heading. Most modern parsers tolerate missing blank lines, but stricter processors — and some Jekyll/Hugo setups — will merge the heading into the paragraph above it if you skip this.
Some paragraph text.
## Correct: blank line above and below
More paragraph text.
If you're brand new to Markdown, start with Markdown Basics first.
Heading Levels: What Each One Is Actually For
| Level | Syntax | HTML | Use it for |
|---|---|---|---|
| H1 | # Heading |
<h1> |
The page/document title — one per document |
| H2 | ## Heading |
<h2> |
Main sections |
| H3 | ### Heading |
<h3> |
Subsections under an H2 |
| H4 | #### Heading |
<h4> |
Supporting detail under an H3 |
| H5 | ##### Heading |
<h5> |
Rare — deep technical references only |
| H6 | ###### Heading |
<h6> |
Rarest — avoid unless the document genuinely needs 6 levels |
Most README files and blog posts never need past H3. If you're reaching for H5/H6 regularly, that's usually a signal to split the document (see When to Split Large Documents), not to keep nesting.
ATX vs. Setext Headings
| Style | Syntax | Levels supported | Recommended |
|---|---|---|---|
| ATX | # Heading |
H1–H6 | |
| Setext | Heading + === or --- on the next line |
H1–H2 only | Only if maintaining legacy content |
Project Title
=============
Installation
------------
= produces an H1, - produces an H2. Because Setext caps out at two levels, it can't represent real document hierarchy — treat it as legacy syntax, not a style choice.
Building a Logical Heading Structure
Treat headings as an outline, not a font-size picker — pick a level because of where the content sits in the document, never because a heading "looks right" on screen. If you want different visual styling without changing structure, that's a CSS/theme decision, not a heading-level decision.
# User Guide
## Getting Started
### Installation
### Configuration
## Features
### Authentication
### API Access
## Troubleshooting
Each H2 is a major topic; each H3 splits it further. Skipping levels (H2 straight to H4) is the most common hierarchy mistake — see Common Heading Mistakes.
When to Split Large Documents
If a document regularly reaches H5 or H6, it's usually doing too much in one page. Split it into linked pages instead — e.g., one monolithic API guide becomes separate pages for Authentication, Endpoints, and Error Codes, each with its own H1 and a shallow H2/H3 outline.
Preview your outline in the live editor before publishing, and link related pages through a Documentation Index.
Real-World Heading Examples
A project README:
# My Project
## Features
## Installation
## Quick Start
## Configuration
## Usage
## Contributing
## License
A technical API reference (deeper nesting, justified by content volume):
# API Documentation
## Authentication
### API Keys
### OAuth
## Endpoints
### Users
### Projects
### Billing
A blog post converted to HTML (SEO-driven structure — see next section):
# 10 Ways to Speed Up Your Website
## 1. Optimize Images
## 2. Enable Caching
## 3. Minify CSS and JavaScript
Notice the pattern: H1 count never exceeds one, and H2 count matches the number of real, distinct sections a reader would look for — not the number of paragraphs.
Headings and On-Page SEO
When Markdown compiles to HTML — for a blog, docs site, or CMS — headings stop being just visual structure and become on-page signals search engines use to parse the page.
- Use exactly one H1 per page, matching the primary topic. HTML5's sectioning model technically permits multiple H1s, and some component-based frameworks generate them by accident — but a single H1 remains the strongest, most consistently supported signal for search visibility.
- Write H2s in the language people actually search — "How to Add a Table of Contents" ranks better than a vague "More Info."
- Don't repeat near-identical headings across a page purely for keywords; it reads as duplication to both readers and crawlers.
- Keep the hierarchy unbroken — search engines pull heading structure into featured snippets, so a broken H1→H4 jump can mean your best content gets skipped for one.
Anchor Links and Custom Heading IDs
Most platforms auto-generate a clickable anchor for every heading, letting readers jump straight to a section — this is platform behavior, not part of the original Markdown spec.
How auto-anchors work:
## Installation Guide
That generates an anchor like #installation-guide, which you link to as:
[Go to Installation](#installation-guide)
GitHub's specific algorithm — GitHub builds the anchor ID by: lowercasing the text, replacing spaces with hyphens, stripping most punctuation, and appending -1, -2, etc. if the same heading repeats.
| Heading | Generated anchor |
|---|---|
## Installation Guide |
#installation-guide |
## API Reference |
#api-reference |
Repeated ## Installation |
#installation, #installation-1, #installation-2 |
Overriding the anchor with a custom ID — the one thing most guides don't cover. If you rename a heading later, the auto-generated anchor changes and every external link to the old one breaks. Setting a custom ID keeps the anchor stable even when the visible text changes:
| Platform | Custom ID support | Syntax |
|---|---|---|
| GitHub, GitLab | — | |
| kramdown (Jekyll) | ## Installation {#install-guide} |
|
| Pandoc | ## Installation {#install-guide .no-toc} |
|
| Docusaurus | ## Installation {#install-guide} |
|
MkDocs (with toc extension) |
## Installation then {: #install-guide } on the next line |
Rule of thumb: if you're publishing on a platform that supports custom IDs, set one for any heading likely to be renamed or linked from outside the page.
Heading Behavior Across Platforms
Syntax is universal; auto-TOC generation is not:
| Platform | Auto TOC |
|---|---|
| GitHub | Yes, for supported file types |
| GitLab | |
| Jekyll | Via plugin |
| Hugo | Via shortcode/theme — varies significantly by theme |
| Docusaurus | Yes, built-in |
| MkDocs | Yes, built-in |
| GitBook | Yes, built-in |
| Obsidian | Via plugin/outline view |
| Notion | Via linked TOC block |
If you're publishing docs on a static site generator, verify your specific theme's anchor and TOC behavior before hardcoding #anchor links — Hugo in particular varies by theme.
Inline Formatting in Headings
Most parsers allow links, inline code, and emoji inside headings:
## [API Documentation](https://example.com)
## Installing `npm`
## 🚀 Getting Started
Widely supported on GitHub, GitLab, and Obsidian — but avoid stacking multiple formatted elements in one heading, since it hurts scannability and can break some auto-generated TOC renderers.
Headings and Table of Contents
Separate from this site's own page navigation — this is about the TOC generated inside your own Markdown document when it's rendered. A consistent hierarchy is what makes that possible:
# User Guide
## Installation
## Configuration
### Environment Variables
## Troubleshooting
That produces a nav tree that mirrors the outline exactly. Generate one automatically with the Markdown TOC Generator.
Accessibility and Document Structure
Screen readers jump directly from one heading to the next by level — so a broken hierarchy (H2 straight to H4) isn't just a visual inconsistency, it's a navigation dead-end for assistive tech users. Keep every level sequential and reserve H1 for the single page title.
Common Heading Mistakes
| Mistake | Fix |
|---|---|
##Heading (no space) |
## Heading |
| Skipping H1 → H4 | Follow the sequence: H1 → H2 → H3 |
| Multiple H1s on one page | One H1, reserved for the page title (see Headings and SEO) |
| No blank line before/after heading | Add blank lines for parser compatibility |
| Long, paragraph-style heading text | Keep headings short and scannable |
For general Markdown formatting slips beyond headings, see Common Markdown Mistakes.
Best Practices Checklist
- Follow H1 → H2 → H3 in sequence — never skip a level
- One space after every
#, blank line before and after each heading - Set a custom anchor ID (
{#id}) if the heading text may change later - Keep heading text under ~60 characters where possible
Frequently Asked Questions
- 1
Can I use multiple H1 headings in one document?
Most parsers allow it technically, but for SEO and consistent document structure, use exactly one H1 as the page title and organize everything else with H2/H3.
- 2
What's the real difference between ATX and Setext headings?
ATX (#) supports all six levels and is the modern standard. Setext (=== / ---) only supports H1 and H2 and is considered legacy syntax — use it only when editing older documents that already use it.
- 3
How do I stop my anchor link from breaking when I rename a heading?
Set a custom heading ID where your platform supports it ({#custom-id} in kramdown, Pandoc, Docusaurus; {: #id } in MkDocs). GitHub and GitLab don't support custom IDs — the anchor is always auto-generated from the current heading text.
- 4
Do Hugo and Jekyll generate heading anchors the same way as GitHub?
No. Both use their own slug logic (often theme- or plugin-dependent for Hugo, kramdown-based for Jekyll), so a heading that produces #my-heading on GitHub may generate a different anchor on your static site — always verify in your local build.
- 5
Does heading structure actually affect SEO?
Yes, indirectly — search engines use heading hierarchy to understand page structure and can pull H2/H3 text into featured snippets. A single clear H1 and logically nested H2s help; keyword-stuffed or duplicated headings don't.
- 6
Why isn't my heading rendering at all?
Almost always a missing space after #, more than six # characters, or a missing blank line before the heading in a stricter parser. Check all three first.
