Documentation
Home · Docs · Markdown · Markdown headings: ATX and Setext syntax

Markdown headings: ATX and Setext syntax

How to write headings in Markdown with the hash symbol for H1 to H6, plus the Setext underline style, heading IDs, and the HTML output.

A heading in Markdown is a title or section label, created by starting a line with one to six hash characters (#) followed by a space and the heading text. The number of hashes sets the level, so # is a top-level heading (H1) and ###### is the smallest (H6). A second style, Setext, underlines text with = or - for level 1 and level 2 headings.

Markdown headings syntax reference

Goal Syntax
Heading level 1 # Heading
Heading level 2 ## Heading
Heading level 3 ### Heading
Heading level 6 ###### Heading
Setext level 1 Heading then =======
Setext level 2 Heading then -------

Markdown headings key facts

  • Start a line with one to six # characters and a space, so # Title is a heading and the text follows the space.
  • The number of hashes sets the level: one hash is an H1, two is an H2, three is an H3, and six is an H6.
  • Markdown supports six heading levels, matching the HTML elements <h1> through <h6>. Seven or more hashes do not create a heading.
  • CommonMark and GitHub Flavored Markdown require a space after the hash. Use # Heading; older parsers may accept #Heading, but modern CommonMark-compatible parsers do not treat it as a heading.
  • Setext headings underline the text with = for level 1 or - for level 2, covering only those two levels.
  • Leave a blank line above and below each heading so parsers separate it cleanly from the surrounding text.

Build a heading hierarchy, not a visual size system

Use one descriptive H1 for the page title, then organise sections with H2 and subsections with H3. Do not skip from H2 to H4 merely to obtain a smaller appearance. Heading levels describe document structure and help screen-reader users, search engines, and generated tables of contents understand the page.

ATX headings with # work at all six levels and are easier to maintain than Setext headings. Keep headings concise, specific, and unique where possible because many renderers turn them into heading IDs. Put blank lines around headings to avoid parser differences in tightly formatted source.

Basic syntax

To create an ATX heading, start a line with one to six # characters, add a space, then write the heading text.

Markdown

# Heading level 1
## Heading level 2
### Heading level 3

Rendered output

Heading level 1
Heading level 2
Heading level 3

HTML output

<h1>Heading level 1</h1>
<h2>Heading level 2</h2>
<h3>Heading level 3</h3>

Include a space between the # characters and the text. In CommonMark and GitHub Flavored Markdown, #Heading with no space is not a heading and renders as plain text. Add a blank line before and after each heading so parsers separate it cleanly from surrounding paragraphs.

All six heading levels

Markdown supports six heading levels, matching the HTML elements <h1> through <h6>.

Markdown

# Heading level 1
## Heading level 2
### Heading level 3
#### Heading level 4
##### Heading level 5
###### Heading level 6

Rendered output

Heading level 1
Heading level 2
Heading level 3
Heading level 4
Heading level 5
Heading level 6

Seven or more # characters do not create a heading. In most parsers, ####### Text renders as a plain paragraph starting with seven hashes.

Setext headings

Setext headings underline the text instead of prefixing it. A line of = characters below the text makes a level 1 heading, and a line of - characters makes a level 2 heading. Setext only supports these two levels.

Markdown

Heading level 1
===============

Heading level 2
---------------

Rendered output

Heading level 1
Heading level 2

HTML output

<h1>Heading level 1</h1>
<h2>Heading level 2</h2>

The underline can be any length of one or more characters, so = and ==== both work. Because a single - on its own line can be read as a Setext level 2 underline, always leave a blank line between a paragraph and a following list or horizontal rule to avoid the previous line being turned into a heading unexpectedly.

Optional closing hashes

ATX headings can have a closing sequence of # characters, which most people omit. The closing hashes are decorative and do not need to match the opening count.

Markdown

# Heading with closing hashes #
### Counts do not need to match #########

Rendered output

Heading with closing hashes
Counts do not need to match

The closing sequence is stripped from the rendered text, so both examples produce clean headings with no trailing hashes.

Headings with inline formatting

Heading text can contain inline elements such as emphasis, inline code, and links.

Markdown

# A heading with **bold**, *italic*, and `code`
## See the [documentation hub](/docs/markdown/)

Rendered output

A heading with bold, italic, and code

Flavor differences

Both ATX and Setext headings are widely supported. The meaningful differences are whether a space is required after the # and how strictly each parser follows the rules. Check the named renderer when an older Markdown flavor is part of the publishing workflow.

Flavor ATX (#) Setext (= / -) Notes
CommonMark Yes Yes Requires a space after #. Levels 1 to 6. Setext levels 1 and 2 only. Optional closing hashes.
GitHub Flavored Markdown (GFM) Yes Yes Superset of CommonMark, so the same rules apply.
MultiMarkdown Yes Yes Supports both styles. Can attach heading IDs (covered on the heading IDs page).
Markdown Extra Yes Yes Supports both styles. Can attach custom IDs with {#id} syntax.
Pandoc Yes Yes Supports both. Setext is limited to levels 1 and 2. Can emit either style on output.

The main practical difference is the space after #. Classic Markdown (John Gruber's original) accepted #Heading without a space, but CommonMark, GFM, and most modern renderers require the space. Writing # Heading with the space works across CommonMark, GFM, and most modern Markdown renderers.

Custom heading IDs (anchors) are a flavor-specific extension, not core heading syntax. See heading IDs in Markdown for how MultiMarkdown, Markdown Extra, and Pandoc attach them.

Other platforms (Slack, Notion, Obsidian)

Headings behave differently in some tools people paste Markdown into:

  • Slack: does not support # headings. A line starting with # Text renders as literal text with the hash visible.
  • Notion: supports #, ##, and ### (three levels) when typed as shortcuts, but does not render #### or smaller as headings.
  • Obsidian: full support for all six ATX levels and Setext, matching CommonMark.

For the full comparison of how flavors differ across all elements, see Markdown flavors.

How Markdific renders it

Markdific renders standard ATX and Setext headings as styled heading elements sized by level, from <h1> down to <h6>. Headings that contain inline code, emphasis, or links keep that inline formatting inside the rendered heading.

Try it in the Markdific online editor.

Common mistakes and gotchas

  • No space after the hash. #Heading is plain text in CommonMark and GFM. Always write # Heading with a space.
  • Too many hashes. Seven or more # characters do not make a heading. The maximum is six.
  • Missing blank line before the heading. Some parsers need a blank line between a paragraph and a heading, or the # line is pulled into the preceding paragraph.
  • Accidental Setext heading. A paragraph immediately followed by a line of - or = characters becomes a Setext heading. Leave a blank line before horizontal rules and lists to avoid this.
  • Skipping levels for styling. Jumping from # to #### to get a smaller size breaks the document outline. Use levels in order and style with CSS instead.
  • Trailing hashes you did not intend. A closing sequence like # Heading # is fine, but stray hashes mid-line stay as literal text.

Best practices

  • Use ATX headings (#) as the default. They are explicit, support all six levels, and read clearly in source.
  • Always put a space after the # characters.
  • Use exactly one H1 per document for the page title, then nest sections with H2, H3, and deeper in order.
  • Do not skip heading levels. A logical outline helps readers, screen readers, and search engines.
  • Leave a blank line above and below each heading.
  • Reserve Setext for level 1 and level 2 only, and prefer ATX if you need consistency across all levels.

HTML equivalent

Markdown headings compile to the HTML heading elements <h1> through <h6>. The heading level maps directly to the element number.

Markdown

# Title
## Section
### Subsection

HTML output

<h1>Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>

FAQ

How do you create a heading in Markdown? Start a line with one to six # characters followed by a space and the heading text. One hash is a level 1 heading and six hashes is a level 6 heading.

How do you make an H1, H2, or H3 heading in Markdown? Match the number of hashes to the level: # Title is an H1, ## Section is an H2, and ### Subsection is an H3. Always keep the space after the hashes.

How many heading levels does Markdown support? Six, matching the HTML elements <h1> through <h6>. Seven or more # characters do not create a heading.

What is the difference between ATX and Setext headings? ATX headings prefix the text with # characters and support all six levels. Setext headings underline the text with = for level 1 or - for level 2 and support only those two levels.

Do you need a space after the hash in a Markdown heading? Yes in CommonMark, GitHub Flavored Markdown, and most modern renderers. Classic Markdown allowed #Heading without a space, but # Heading with the space is the portable form.

Why is my Markdown heading not rendering? The most common causes are a missing space after the #, more than six hashes, or a missing blank line before the heading line. Check those first.

How do you add an ID or anchor to a Markdown heading? Core Markdown has no built-in syntax. MultiMarkdown, Markdown Extra, and Pandoc support custom IDs, and some renderers auto-generate them. See the heading IDs page for details.

What HTML does a Markdown heading produce? An <h1> to <h6> element matching the level, with any inline formatting such as bold, code, or links preserved inside it.

Can a heading contain bold, links, or code? Yes. Heading text supports inline formatting, so you can include emphasis, inline code, and links inside a heading.

Sources and compatibility references

Use these primary references for syntax rules. Renderer behaviour can still vary by version and configuration, so preview important documents in their final destination.