Articles
Published · July 22, 2026

Markdown Heading Levels: ATX vs Setext Explained

Markdown gives you two ways to write a heading: hash marks in front of the text, or an underline beneath it. They aren't interchangeable — one supports six heading levels, the other only two. Here's the exact syntax for both, where each comes from, and which one to reach for in your own notes and docs.

Quick answer (one-paragraph TL;DR)

Markdown has two ways to write a heading. ATX puts one to six # characters in front of the text — # for an H1, ## for an H2, all the way to ###### for an H6. Setext puts an underline of = or - beneath a line of text instead, but it only covers two levels: = for H1, - for H2. Most documents need more than two levels of structure, and ATX is the only syntax that gets there. The same "structure the document, don't just decorate it" logic matters for keeping long documents navigable — headings are the backbone readers and tools both rely on to scan a file.

How it actually works

ATX headings start with 1-6 unescaped # characters, followed by at least one space, then the heading text:

# Level 1
## Level 2
### Level 3

Per the CommonMark spec, that required space matters. A line like #5 bolt has no space after the hash, so it's parsed as an ordinary paragraph, not a heading. An ATX heading can also carry an optional closing sequence of # characters — purely cosmetic, and it doesn't need to match the opening count, so ## Level 2 ## and ## Level 2 render identically. One more practical detail: an ATX heading can interrupt a paragraph directly, with no blank line required first.

Setext headings work the other way around — text first, then an underline on the next line:

Level 1
=======

Level 2
-------

The original Markdown syntax documentation describes this as "underlining" the heading text: any number of = characters makes an H1, any number of - characters makes an H2. Unlike ATX, a Setext heading can't interrupt a paragraph. CommonMark requires a blank line between a preceding paragraph and the line that becomes Setext text, or the underline gets read as something else entirely — a list marker, in the case of a lone -.

When to use it (and when to skip it)

The deciding factor is usually simple: Setext only goes two levels deep. There's no underline character defined for H3, H4, H5, or H6 — the syntax just stops at -. The moment a document needs a sub-section under a sub-section, Setext can't express it, and the writer has to switch to ATX for the deeper levels anyway. Most people find it simpler to use ATX throughout rather than mix both styles in one file.

ATX also has a practical edge for skimming: the number of # characters shows a heading's depth at a glance, even in a raw text file with no rendering. That matters when you're escaping a literal `#` at the start of a line that isn't meant to be a heading at all — knowing the exact rule, a required space after the hashes, is what tells you whether a line is a heading or just starts with a hash character.

One notable variation: GitHub Flavored Markdown allows the text above a Setext underline to span multiple lines, which plain CommonMark and many older Markdown tools don't support. Worth knowing if a document written for GFM ever gets pasted somewhere with a stricter parser.

How Carets fits in

Carets renders both ATX and Setext headings correctly as you type, so a document written either way — or pasted in from somewhere else — shows up with the right structure immediately, not just after export.

Two things help specifically here for anyone building out longer notes or docs. Carets is native and fast on iPhone, iPad, and Mac, so scrolling through a long, heading-heavy document to jump between sections stays quick instead of laggy. And because everything stays organized in files, projects, and tags rather than one long scroll, a document's heading structure and its place in your broader notes both stay visible at once — useful once a file has moved past two heading levels and actually needs ATX's full range. Files stay plain text throughout, so nothing about the heading syntax itself is locked into Carets; it opens correctly anywhere. See also other Markdown syntax choices worth knowing if you're standardizing a house style beyond headings.

Carets is available on the App Store for iPhone, iPad, and Mac.

Frequently Asked Questions

What's the difference between ATX and Setext headings in Markdown?

ATX headings use one to six # characters at the start of a line, with the number of hashes matching the heading level (# is H1, ## is H2, and so on through ###### for H6). Setext headings instead underline a line of text with = for a level-1 heading or - for a level-2 heading. ATX supports all six heading levels; Setext only supports two.

Can Setext headings go deeper than level 2?

No. Setext syntax only defines an underline for level 1 (=) and level 2 (-). There's no underline character for level 3 or deeper, so any document that needs H3 or lower must use ATX-style # headings for those levels, even if it uses Setext for H1 and H2.

Do ATX headings need a space after the hash marks?

Yes — per the CommonMark spec, at least one space or tab is required between the opening # characters and the heading text, unless the heading is empty. Without that space, something like #5 bolt is parsed as a plain paragraph, not a heading.

Which heading style should I use in my own Markdown files?

ATX is the more common and more flexible choice today: it shows the heading level explicitly at a glance, supports all six levels, and can interrupt a paragraph without a blank line first. Setext can look cleaner for a lone top-level title, but it becomes awkward once a document needs sub-sections — which is why most style guides and generators default to ATX.

Does GitHub Flavored Markdown handle headings differently from standard Markdown?

GFM follows the CommonMark rules for ATX headings with no changes. Its one notable difference is Setext headings: GFM allows the text above the underline to span multiple lines, which base CommonMark and many older Markdown implementations don't support.

Conclusion

ATX and Setext both create the same underlying heading elements. The difference is entirely in the syntax and how far each one can go. ATX's # marks scale to six levels and read clearly even in a raw file; Setext's underlines top out at two and work best for a short, flat document with a single title. Pick one style, stay consistent within a document, and heading levels will keep doing their real job — making a file easy to navigate, whether that's you scrolling back through your own notes or a tool parsing the structure automatically.