Skip to content

MD003 - Heading style should be consistent

Aliases: heading-style

What this rule does

Ensures all headings in your document use the same formatting style.

Why this matters

  • Readability: Consistent heading styles make documents easier to scan and navigate
  • Professionalism: Mixed heading styles look unprofessional and unpolished
  • Tool compatibility: Some tools expect consistent heading formats for navigation features

Examples

✅ Correct (using # style)

# First Level Heading
## Second Level Heading
### Third Level Heading

✅ Correct (using underline style)

First Level Heading
==================

Second Level Heading
------------------

✅ Correct (using setext-with-atx style)

First Level Heading
==================

Second Level Heading
------------------

### Third Level Heading
#### Fourth Level Heading

✅ Correct (using setext-with-atx-closed style)

First Level Heading
==================

Second Level Heading
------------------

### Third Level Heading ###
#### Fourth Level Heading ####

❌ Incorrect (mixed styles)

# First Level Heading

Second Level Heading
------------------

### Third Level Heading

🔧 Fixed

# First Level Heading
## Second Level Heading
### Third Level Heading

Configuration

[MD003]
style = "consistent"  # Options: "consistent", "atx", "atx-closed", "setext", "setext-with-atx", "setext-with-atx-closed"

Style options explained

  • "consistent" (default): Use the most prevalent style in your document (in case of a tie, ATX style is preferred as it's most widely supported)
  • "atx": Use # symbols (# Heading)
  • "atx-closed": Use # symbols at both ends (# Heading #)
  • "setext": Use underlines (equals for level 1, dashes for level 2)
  • "setext-with-atx": Use underlines for level 1-2, # symbols for level 3-6
  • "setext-with-atx-closed": Use underlines for level 1-2, # symbols with closing # for level 3-6

Note: Underline style only works for level 1 and 2 headings. Level 3 and below must use # symbols.

Automatic fixes

This rule can automatically convert all headings to match your configured style or the most prevalent style in the document.

Markdown with Gherkin

Under the mdg flavor, MD003 steers every heading to plain ATX whatever style is configured. Markdown with Gherkin parses ATX headings only — one to six # characters followed by a space — so a Setext heading never becomes a Gherkin node, and a closing sequence leaks into the node's name, making # Feature: F # a node named F #.

When a fixed style other than atx was configured explicitly and the override applies in MDG, rumdl prints one [config warning] per process. The default consistent style and an explicit atx do not warn.

❌ Incorrect

Feature: Checkout
=================

## Scenario: Purchase ##

✅ Correct

# Feature: Checkout

## Scenario: Purchase

Under any other flavor the configured style is applied as usual.

Limitation

Converting a Setext heading changes its source text, not just its Markdown presentation. In an MDG document without an explicit # Feature: heading, Cucumber may derive the feature name from the leading Markdown content; adding the ATX marker can therefore add a literal # and following space to that parsed feature name. This occurs in Cucumber's testdata/good/misc.feature.md. Avoid Setext headings in MDG, or disable MD003 when that AST must remain unchanged.

See Markdown with Gherkin Flavor for the full flavor specification.

Learn more

  • MD001 - Heading levels should only increment by one
  • MD022 - Headings should be surrounded by blank lines
  • MD023 - Headings must start at the beginning of the line