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.
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
- A space or tab after the hashes is required.
#Headingis 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.
## Heading with closing hashes ##
### Up to 3 spaces of indent is fineHeading 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.
Heading 1
=========
Heading 2
---------Heading 1
Heading 2
--- 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
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.
| Heading | GitHub anchor | Why |
|---|---|---|
| ## Getting Started | #getting-started | Lowercased, space becomes a hyphen |
| ## What's New in v2.0? | #whats-new-in-v20 | Apostrophe, dot, and ? are removed |
| ## C++ & Rust | #c--rust | + and & are removed, but both spaces still become hyphens |
| ## _Helpful_ Tips | #helpful-tips | Markup is stripped before the slug is built |
| ## snake_case-name | #snake_case-name | Hyphens and underscores are kept |
| ## FAQ | #faq-1 | The second ## FAQ in the file gets -1, the third -2 |
## Installation
[Jump to installation](#installation)
[Setup guide, step 2](docs/setup.md#step-2)<a name="install"></a>
## Installing on Windows
[Jump to the install steps](#install)- 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.
- [Overview](#overview)
- [Installation](#installation)
- [Usage](#usage)
## Overview
Intro paragraph.
## Installation
Steps to install.
## Usage
How to use it.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
/tocblock, 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
#HeadingFixed
# HeadingBroken
####### Too many levelsFixed
###### Maximum six levelsBroken
## Indented four spacesFixed
## Not indentedBroken
Some intro text
---Fixed
Some intro text
---Broken
# Heading 1
### Heading 3Fixed
# Heading 1
## Heading 2
### Heading 3Broken
## My Heading {#my-heading}Fixed
<a name="my-heading"></a>
## My HeadingQuick Reference
| Syntax | Level | Notes |
|---|---|---|
| # Heading | H1 | ATX syntax; most common |
| ## Heading | H2 | ATX syntax |
| ### Heading | H3 | Smallest level Discord renders |
| ###### Heading | H6 | Maximum ATX level |
| ## Heading ## | H2 | Closing hashes are optional and ignored |
| Heading\n========= | H1 | Setext syntax |
| Heading\n--------- | H2 | Setext 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.
| Platform | Supported? | Notes |
|---|---|---|
| GitHub | Yes | All six levels plus setext. Every heading gets an automatic anchor, and the file header's Outline menu lists them as a table of contents. |
| GitLab | Yes | Same syntax and anchor rules. [[_TOC_]] or [TOC] on its own line inserts a table of contents. |
| Yes | All six levels in the Markdown editor and on old.reddit.com. The default Rich Text editor uses its toolbar instead of typed #. | |
| Discord | Partial | # , ## and ### only, plus -# for small subtext. #### and deeper stay as plain text. The space after the hashes is required. |
| Slack | Partial | No 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. |
| Notion | Partial | Typing # , ## or ### plus a space creates Heading 1-3 blocks. Notion also has a Heading 4 block. There is no H5 or H6. |
| Obsidian | Yes | All six levels, listed in the Outline view. Link to one with [[Note#Heading]] (the heading text, not a slug). |
| VS Code preview | Yes | Renders ATX and setext headings. The Outline view lists them, and link completions suggest #heading anchors. |
| CommonMark | Yes | Defines ATX headings (# to ######) and setext headings (underlined with = or -). Heading ids are not part of the spec. |
| Docusaurus / VitePress / Astro | Yes | Auto-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.