Markdown Header Syntax
When writing Markdown documents, headers (also called headings) are one of the most frequently used elements. Whether you're writing a README, a technical blog post, or project documentation, headers are the fundamental building blocks for organizing your content. This article covers everything about Markdown header syntax — not just "how to write them," but also "why it works that way" and "where things can go wrong."
Basic Markdown Header Syntax
Markdown supports two header styles: ATX style and Setext style. ATX is what the vast majority of people use, while Setext is less common but still valid.
ATX Style: Creating Headers with
ATX style uses 1 to 6 # symbols at the beginning of a line to indicate the heading level, corresponding to HTML's <h1> through <h6>:
## Heading Level 2
### Heading Level 3
#### Heading Level 4
##### Heading Level 5
###### Heading Level 6This renders as six levels of headings, from largest to smallest. Level 1 is the biggest and boldest, and level 6 is the smallest.
A few important things to note:
- There must be a space between the
#and the heading text. Writing#Headingwon't work in most renderers — it will just be treated as plain text. Honestly, this was the most common mistake I made when I first started using Markdown. I'd write#and wonder why it didn't render as a heading. It was always the missing space. - The number of
#symbols determines the heading level: 1#is level 1 (h1), 6#symbols is level 6 (h6). - You can optionally add any number of
#symbols after the heading text as a "closing" marker, but this is optional and doesn't affect the rendered result:
## This is an h2 heading ##
### The trailing # symbols are optional ###Setext Style: Creating Headers with Underlines
Setext style only supports level 1 and level 2 headings. You write the heading text on one line, then add equals signs (=) or hyphens (-) on the next line:
This is a Level 1 Heading
==========================
This is a Level 2 Heading
--------------------------Equals signs create a level 1 heading, and hyphens create a level 2 heading. The number of underline characters doesn't matter — one character works just as well as a full line.
Setext style has great readability in plain text — it looks like the traditional way of underlining headings in printed documents. However, in most modern Markdown tutorials and tools, ATX style is far more common. My recommendation: if you're just writing documents, stick with ATX style consistently. It's simpler and harder to mess up. Just know that Setext exists so you can read other people's documents.
Comparing the Two Styles
| Feature | ATX Style | Setext Style |
|---|---|---|
| Supported levels | h1 through h6 | Only h1 and h2 |
| Syntax | # prefix | = or - underline |
| Readability | Level is obvious in source | Looks like traditional headings in plain text |
| Popularity | Most widely used | Rarely used |
| Editing ease | Add/remove # to change level | Need to add/remove entire underline line |
How to Use Markdown Heading Levels Properly
Knowing the syntax is one thing, but the more important question is: how should you use the six heading levels? This involves document structure, SEO, and accessibility.
The Semantic Meaning of Heading Levels
Heading levels are not just about "font size." They represent the hierarchical structure of your document. In HTML, <h1> through <h6> form the page's outline, and both search engines and screen readers rely on this structure to understand the content.
A good heading structure should look like this:
# Document Title (h1)
## Section One (h2)
### A Subtopic in Section One (h3)
### Another Subtopic in Section One (h3)
## Section Two (h2)
### A Subtopic in Section Two (h3)You should not skip levels, like going from h1 directly to h3:
# Document Title (h1)
### Jumping straight to h3? ❌While this won't cause a syntax error, skipping levels creates a confusing document structure that's bad for both SEO and accessibility.
Practical Usage Tips
- A document typically has only one h1. It's the main title, like a book's title.
- Use h2 for major sections, like chapters in a book.
- Use h3 for subsections, like sections within a chapter.
- Use h4 through h6 as needed. Most documents don't need that many levels of depth.
I once helped a project organize their documentation and found they were jumping from h1 straight to h4 — because "the h4 font size looked about right." This is confusing the semantic purpose of headings with their visual appearance. Heading levels reflect content structure; font size should be controlled through CSS.
Header Anchors and In-Page Links
In many Markdown rendering environments, headings automatically generate anchors, and you can use links to jump directly to a specific heading's position.
Default Behavior on GitHub and Most Platforms
On platforms like GitHub and GitLab, headings automatically get an ID. The rule is: convert the heading text to lowercase, remove special characters, and replace spaces with hyphens. For example:
## Markdown Header SyntaxThe auto-generated anchor is #markdown-header-syntax. On GitHub, the anchor handling for non-English headings may differ from English-only ones. If you need precise linking, it's best to check the actual anchor value in your browser.
Custom Heading IDs
Some Markdown extensions support custom heading IDs, such as Pandoc and PHP Markdown Extra:
## My Heading {#custom-id}Then you can create an in-page link with [Jump to My Heading](#custom-id).
Renderer Compatibility Differences
One of the biggest "gotchas" with Markdown is that different renderers may handle the same syntax differently. While header syntax is relatively simple, there are still differences.
The Space Issue
The requirement for a space after # is enforced differently across renderers:
| Renderer | #Heading (no space) | # Heading (with space) |
|---|---|---|
| CommonMark | ❌ Not recognized | ✅ Renders correctly |
| GFM (GitHub) | ❌ Not recognized | ✅ Renders correctly |
| Legacy Markdown.pl | Partially recognized | ✅ Renders correctly |
| markdown-it | ❌ Not recognized | ✅ Renders correctly |
The conclusion is simple: always add a space. No need to overthink it.
Blank Lines Around Headings
In most renderers, headings are correctly recognized without requiring blank lines before or after them. However, in some implementations (like the Flexmark parser), a heading immediately following a code block, list, or blockquote may be misinterpreted. The safe approach is to leave a blank line before and after each heading.
Setext Style Support
Although Setext is part of the original Markdown specification, some lightweight Markdown parsers don't support it. If you're certain the target platform supports it (like GitHub, GitLab, or CommonMark-compatible renderers), using Setext is fine. If you're not sure, stick with ATX.
Frequently Asked Questions
How Many Heading Levels Does Markdown Support?
Six levels, corresponding to ###### (6 # symbols plus a space). The HTML specification also defines only <h1> through <h6>. In practice, most documents only need up to h3 or h4.
Why Isn't My # Heading Rendering?
The most common reason is that there's no space after the #. Make sure you write # Heading instead of #Heading. Another possible reason is indentation spaces before the # — the # should be at the beginning of the line (up to three leading spaces are allowed, but it's best to have none).
Which Is Better: ATX or Setext?
ATX is more practical — it supports six levels, is easy to edit (add/remove # to change levels), and is the mainstream choice. Setext's advantage is readability in plain text, but it only supports two levels. Unless you have a specific preference, stick with ATX consistently.
Can Headings Use Bold or Italic Formatting?
Yes. Heading text can contain inline formatting:
## **Important** Section
### This Feature *Is Deprecated*That said, headings are already emphasized elements by nature, so adding bold formatting can be somewhat redundant. Use it when it makes sense.