Skip to main content

Markdown Heading Syntax

How to create Markdown headings. Covers ATX # syntax and setext underlines, heading anchors and ids, hierarchy, table of contents, and the mistakes that break them.

Basic Syntax

ATX headings use one to six hash characters at the start of a line. The number of hashes sets the level. Always follow the hashes with a space.

ATX headings H1-H6
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
Preview

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6

Setext Headings

The original Markdown syntax supports level 1 and level 2 headings by underlining text with = or -. These are called setext headings. They are valid CommonMark, but less common today because they only cover two levels.

Setext headings
Heading 1
=========

Heading 2
---------
Preview

Heading 1

Heading 2

Heading Hierarchy

A well-structured document uses one H1 for the main title, then H2s for sections, H3s for subsections, and so on. Do not skip levels: H1 to H3 without an H2 creates a broken outline and hurts accessibility.

Recommended outline

  • # Page title
  • ## Section
  • ### Subsection
  • ### Subsection
  • ## Section
Accessibility note: Screen readers use heading levels to navigate. A missing level forces users to guess where they are in the document.

Anchors & IDs

Most modern renderers auto-generate an id for each heading so you can link directly to it. The id is usually the heading text lowercased, with spaces and punctuation replaced by hyphens.

Linking to an auto-generated heading slug
## Installation

[Jump to installation](#installation)
Preview

Installation

Jump to installation

Custom id with raw HTML
<h2 id="install">Installation</h2>
Preview

Installation

To see the exact slug a renderer creates, check the id attribute in the generated HTML. Avoid special characters and keep headings concise so slugs stay predictable.

Table of Contents

A manual table of contents is a list of links to heading ids. It works in any renderer that generates heading anchors, including GitHub, GitLab, Obsidian, and static-site generators.

Markdown table of contents
- [Overview](#overview)
- [Installation](#installation)
- [Usage](#usage)

## Overview

Intro paragraph.

## Installation

Steps to install.

## Usage

How to use it.
Preview

Overview

Intro paragraph.

Installation

Steps to install.

Usage

How to use it.

If you maintain a large docs site, consider a static-site plugin or CI step that regenerates the TOC so headings and links do not drift out of sync.

Common Mistakes

Heading errors are usually spacing, level count, or hierarchy issues. Each row shows the broken version and the corrected one side by side.

Broken

#Heading

Fixed

# Heading
ATX headings need a space between the hash character and the heading text. Without it, the line renders as plain text.

Broken

####### Too many levels

Fixed

###### Maximum six levels
Markdown supports six heading levels. Seven or more hashes do not create a heading and render literally.

Broken

# Heading 1

### Heading 3

Fixed

# Heading 1

## Heading 2

### Heading 3
Markdown syntax allows skipping levels, but it breaks the document outline and confuses screen readers.

Broken

## My Heading {#my-heading}

Fixed

## My Heading

<h2 id="my-heading">My Heading</h2>
Pandoc-style {#id} syntax is not standard Markdown or GFM. Use the renderer's auto-generated slug or raw HTML for a custom id.

Quick Reference

SyntaxLevelNotes
# HeadingH1ATX syntax; most common
## HeadingH2ATX syntax
###### HeadingH6Maximum ATX level
Heading\n=========H1Setext syntax
Heading\n---------H2Setext syntax
[Link](#heading)-Link to auto-generated heading slug
<h2 id="x">Heading</h2>-Custom id via raw HTML

Platform Support

Headings are part of standard Markdown, so they work in most document renderers. Chat apps do not support them.

PlatformSupported?Notes
GitHubYesATX and setext headings work. GitHub Flavored Markdown auto-generates slug ids from heading text.
GitLabYesSame as GitHub Flavored Markdown.
RedditYesSupports ATX headings in posts and comments.
VS Code previewYesRenders ATX and setext headings. Markdown preview outline view uses the heading hierarchy.
ObsidianYesATX headings turn into outline nodes. Obsidian auto-generates heading block links such as #^heading.
NotionPartialPasting Markdown converts # into Notion heading blocks. Live Markdown typing is not supported.
DiscordNoDiscord messages do not support headings.
SlackNoSlack mrkdwn does not support headings.
CommonMarkYesDefines ATX headings (# to ######) and setext headings (underlined with = or -).
Docusaurus / VitePress / AstroYesStatic-site generators render headings and often auto-generate ids and table-of-contents components.

Frequently Asked Questions

How do you create headings in Markdown?

Use ATX syntax: one to six hash characters followed by a space and the heading text, like # Heading 1 or ## Heading 2. You can also use setext syntax: underline text with = for a level-1 heading or - for a level-2 heading.

How many heading levels does Markdown support?

Markdown supports six heading levels, from H1 (#) to H6 (######). Seven or more hashes render as plain text, not a heading.

What is the difference between ATX and setext headings?

ATX headings use hash characters at the start of the line and support all six levels. Setext headings are underlined with = or - and only support level 1 and level 2. ATX is more common because it is compact and supports every level.

How do I link to a heading in Markdown?

In most renderers, write a link to the heading's slug: [see installation](#installation) targets ## Installation. On GitHub, GitLab, and many static-site generators, the slug is auto-generated from the heading text, usually lowercased and with spaces replaced by hyphens.

How do I add a custom id to a Markdown heading?

Standard Markdown does not define custom heading ids. For a custom id, write the heading in raw HTML: <h2 id='install'>Installation</h2>. Most HTML-enabled renderers (GitHub, GitLab, Obsidian, VS Code, static-site generators) honor the id. Discord and Slack strip inline HTML, so this trick does not work there.

Can I skip heading levels?

Markdown syntax allows skipping levels, but you should not. A document should move from H1 to H2 to H3 in order. Skipping levels breaks the outline for screen readers, table-of-contents generators, and search crawlers.

How do I make a table of contents in Markdown?

Create a list of links to the auto-generated heading slugs. For example, - [Overview](#overview) links to ## Overview. Many static-site generators can build this automatically; GitHub does not auto-generate a TOC in rendered files.

Do Discord and Slack support Markdown headings?

No. Discord and Slack do not render headings. If you paste # Heading into a message, it appears as literal text. Use bold or all-caps for visual hierarchy in chat instead.

Related Markdown Guides

Headings are the backbone of document structure. For the surrounding syntax and platform guides: