# 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)
```markdown
# First Level Heading
## Second Level Heading
### Third Level Heading
```
### ✅ Correct (using underline style)
```markdown
First Level Heading
==================
Second Level Heading
------------------
```
### ✅ Correct (using setext-with-atx style)
```markdown
First Level Heading
==================
Second Level Heading
------------------
### Third Level Heading
#### Fourth Level Heading
```
### ✅ Correct (using setext-with-atx-closed style)
```markdown
First Level Heading
==================
Second Level Heading
------------------
### Third Level Heading ###
#### Fourth Level Heading ####
```
### ❌ Incorrect (mixed styles)
```markdown
# First Level Heading
Second Level Heading
------------------
### Third Level Heading
```
### 🔧 Fixed
```markdown
# First Level Heading
## Second Level Heading
### Third Level Heading
```
## Configuration
```toml
[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.
## Headings inside blockquotes
Headings inside blockquotes are checked like any other heading, including
headings in nested quotes, in a quote inside a list item, and in a list item
inside a quote. They are held to the configured style, and under
`style = "consistent"` they count toward the prevalent style exactly as
unquoted headings do (a heading inside a list item is counted the same way). A
quoted heading can therefore decide the document style, and a document whose
only headings are quoted is checked too.
```markdown
# Title
> Quoted heading
> --------------
```
Under `consistent` (or `atx`) the quoted setext heading above is reported:
`Heading style should be # Heading, found Heading (underlined with -)`.
Quoted headings are reported without an automatic fix. Rewriting one has to
rebuild the quote prefix on every line it touches, and a setext underline needs
the same prefix as its text, so `rumdl check --fix` leaves quoted headings as
written and the warning remains until you change them by hand. Unquoted headings
in the same document are still fixed.
## 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
```markdown
Feature: Checkout
=================
## Scenario: Purchase ##
```
### ✅ Correct
```markdown
# 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](flavors/mdg.md) for the full flavor
specification.
## Learn more
- [CommonMark specification for headings](https://spec.commonmark.org/0.31.2/#atx-headings) - Technical details about heading syntax
- [Setext headings](https://spec.commonmark.org/0.31.2/#setext-headings) - Details about underline-style headings
## Related rules
- [MD001](md001.md) - Heading levels should only increment by one
- [MD022](md022.md) - Headings should be surrounded by blank lines
- [MD023](md023.md) - Headings must start at the beginning of the line