Skip to main content

Markdown Headings

How to write Markdown headings (often called headers) from H1 to H6. Covers # syntax and setext underlines, how GitHub builds heading anchors, custom ids, tables of contents, and which chat apps render them.

Short answer

Start a line with one to six # characters, a space, and the text: # Heading 1 through ###### Heading 6. The space is required. On GitHub every heading also gets an anchor you can link to: ## Getting Started becomes #getting-started.

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
  • A space or tab after the hashes is required. #Heading is a paragraph.
  • Up to three spaces of indentation are allowed. Four spaces make an indented code block instead.
  • Closing hashes are optional and ignored: ## Title ## is the same as ## Title.
  • CommonMark and GFM do not need a blank line around a heading, but Pandoc requires one before it. Adding blank lines is the portable habit.
Closing hashes and indentation
## Heading with closing hashes ##

   ### Up to 3 spaces of indent is fine
Preview

Heading with closing hashes

Up to 3 spaces of indent is fine

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. The underline can be any length, even a single character.

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

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

Heading 1

Heading 2

The accidental heading: because --- under a line of text is a setext H2, writing a horizontal line directly below a paragraph turns the paragraph into a heading. Leave a blank line above the ---.

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.

Heading Anchors & Links

CommonMark does not give headings ids. Renderers add them: GitHub, GitLab, VS Code, and most static-site generators turn each heading into an anchor you can link to with #anchor. GitHub documents its rules:

  • Letters are lowercased.
  • Each space becomes a hyphen. Other punctuation is removed; hyphens and underscores stay.
  • Formatting is stripped first, so only the heading text counts.
  • A heading whose anchor already exists in the file gets -1, then -2, and so on.
HeadingGitHub anchorWhy
## Getting Started#getting-startedLowercased, space becomes a hyphen
## What's New in v2.0?#whats-new-in-v20Apostrophe, dot, and ? are removed
## C++ & Rust#c--rust+ and & are removed, but both spaces still become hyphens
## _Helpful_ Tips#helpful-tipsMarkup is stripped before the slug is built
## snake_case-name#snake_case-nameHyphens and underscores are kept
## FAQ#faq-1The second ## FAQ in the file gets -1, the third -2
Link to a heading in this file or another file
## Installation

[Jump to installation](#installation)

[Setup guide, step 2](docs/setup.md#step-2)
Custom anchor on GitHub
<a name="install"></a>
## Installing on Windows

[Jump to the install steps](#install)
Preview

Installing on Windows

Jump to the install steps

  • GitHub documents <a name="…"></a> for custom anchors. They do not appear in the file's Outline menu.
  • Pandoc, kramdown (Jekyll), Hugo, MkDocs with attr_list, and Docusaurus accept ## Heading {#custom-id}. GitHub prints that suffix as text.
  • GitLab documents near-identical rules: lowercase, keep only letters, numbers, hyphens and underscores, turn spaces into hyphens, number duplicates from 1.
  • Obsidian links by heading text, not a slug: [[Note#My Heading]], or [text](Note.md#My%20Heading) in Markdown link format.

The link syntax guide covers the rest of the link forms, including relative paths to other files.

Table of Contents

Markdown has no table-of-contents syntax. The portable method is a list of links to heading anchors, which works anywhere headings get ids.

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.

Some platforms build one for you:

  • GitHub adds an Outline menu to the header of every rendered Markdown file with two or more headings. It does not insert a TOC into the file itself.
  • GitLab renders a TOC wherever [[_TOC_]] or [TOC] sits on its own line.
  • Notion has a /toc block, and Obsidian lists headings in its Outline view.
  • Docusaurus and VitePress generate an on-page TOC from your headings automatically.

Common Mistakes

Heading errors are usually spacing, indentation, or a stray underline. Each row shows the broken version and the corrected one side by side.

Broken

#Heading

Fixed

# Heading
CommonMark and GFM require a space (or tab) after the hashes. Without it, the line renders as plain text. Discord and Reddit enforce the same rule.

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

    ## Indented four spaces

Fixed

## Not indented
Up to three spaces of indentation are allowed. Four spaces (or a tab) turn the line into an indented code block, so the hashes show up as code.

Broken

Some intro text
---

Fixed

Some intro text

---
A line of --- directly under text turns that text into an H2 (a setext heading). If you meant a horizontal line, leave a blank line above it.

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

<a name="my-heading"></a>
## My Heading
The {#id} attribute syntax works in Pandoc, kramdown (Jekyll), Hugo, MkDocs with attr_list, and Docusaurus .md files. GitHub and plain CommonMark print it as part of the heading text. On GitHub, use an <a name> anchor instead.

Quick Reference

SyntaxLevelNotes
# HeadingH1ATX syntax; most common
## HeadingH2ATX syntax
### HeadingH3Smallest level Discord renders
###### HeadingH6Maximum ATX level
## Heading ##H2Closing hashes are optional and ignored
Heading\n=========H1Setext syntax
Heading\n---------H2Setext syntax
-# Subtext-Discord only: small grey text
[Link](#heading)-Link to a heading's anchor
[Link](file.md#heading)-Link to a heading in another file
<a name="x"></a>-Custom anchor on GitHub
## Heading {#x}-Custom id in Pandoc, kramdown, Hugo, Docusaurus

Platform Support

Headings are core Markdown, so every document renderer supports them. Chat apps are the exception: Discord renders three levels, and Slack only renders them inside an app's markdown block. See the Discord and Slack guides for the details.

PlatformSupported?Notes
GitHubYesAll six levels plus setext. Every heading gets an automatic anchor, and the file header's Outline menu lists them as a table of contents.
GitLabYesSame syntax and anchor rules. [[_TOC_]] or [TOC] on its own line inserts a table of contents.
RedditYesAll six levels in the Markdown editor and on old.reddit.com. The default Rich Text editor uses its toolbar instead of typed #.
DiscordPartial# , ## and ### only, plus -# for small subtext. #### and deeper stay as plain text. The space after the hashes is required.
SlackPartialNo heading syntax in the message composer or in mrkdwn. An app's Block Kit markdown block renders # headings, but every level at the same size.
NotionPartialTyping # , ## or ### plus a space creates Heading 1-3 blocks. Notion also has a Heading 4 block. There is no H5 or H6.
ObsidianYesAll six levels, listed in the Outline view. Link to one with [[Note#Heading]] (the heading text, not a slug).
VS Code previewYesRenders ATX and setext headings. The Outline view lists them, and link completions suggest #heading anchors.
CommonMarkYesDefines ATX headings (# to ######) and setext headings (underlined with = or -). Heading ids are not part of the spec.
Docusaurus / VitePress / AstroYesAuto-generate heading ids and an on-page table of contents. Docusaurus also accepts {#custom-id} in .md files.

Frequently Asked Questions

How do you create headings in Markdown?

Start a line with one to six hash characters, then a space, then the heading text: # Heading 1, ## Heading 2, down to ###### Heading 6. You can also underline a line of text with === for a level-1 heading or --- for a level-2 heading (setext style).

What is the difference between a Markdown header and a heading?

Nothing. "Header" is the common informal name, and "heading" is the term the CommonMark spec and HTML use. Both mean the # syntax that renders as <h1> to <h6>. Do not confuse it with the HTML <header> element, which Markdown has no syntax for.

How many heading levels does Markdown support?

Six, from H1 (#) to H6 (######). Seven or more hashes render as plain text. Some platforms support fewer: Discord stops at ###, and Notion stops at Heading 4.

Why is my Markdown heading not working?

The usual causes: no space after the hashes (#Heading), four or more spaces of indentation (which makes a code block), seven or more hashes, or the heading sits inside a code block. On Slack, the message composer has no heading syntax at all.

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?

Link to the heading's anchor: [see installation](#installation) targets ## Installation. On GitHub the anchor is the heading text lowercased, with spaces turned into hyphens and other punctuation removed; a repeated heading gets -1, -2 appended. To link to a heading in another file, add the anchor after the path: [Setup](docs/setup.md#installation). Obsidian uses the heading text itself: [[Note#Heading]].

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

Plain Markdown and GitHub have no syntax for it. On GitHub, put an HTML anchor on the line above the heading, <a name="install"></a>, and link to #install; GitHub documents this method. Pandoc, kramdown (Jekyll), Hugo, MkDocs with attr_list, and Docusaurus .md files accept ## Heading {#install} instead.

Can I use bold, links, or code inside a heading?

Yes. Inline formatting works inside headings: ## Install `npm` packages or ## The **fast** way. GitHub strips the formatting when it builds the anchor, so ## The **fast** way links as #the-fast-way.

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 and table-of-contents generators.

How do I make a table of contents in Markdown?

Write a list of links to the heading anchors, for example - [Overview](#overview). Some platforms build one for you: GitHub shows an Outline menu in the header of every rendered Markdown file with two or more headings, GitLab inserts one for [[_TOC_]], Notion has a /toc block, and Docusaurus and VitePress generate an on-page table of contents.

Does Discord support Markdown headings?

Yes, three levels. Start a line with # , ## or ### and a space for big, medium, and small headers, or -# for small grey subtext. #### and deeper are not headings in Discord.

Does Slack support Markdown headings?

Not in normal messages. The message composer and Slack's mrkdwn format have no heading syntax, so # Heading stays literal. Apps can send a Block Kit markdown block, which renders # headings (all levels at the same size), or a header block.