# Multiple H1 headings

> Several <h1>s scatter your page’s topic signal. Keep one <h1> and demote the rest to <h2> so the outline is clear.

_Category: Search visibility · Detector `seo` · Check `h1-multiple` · Severity: info_

When several components each render their own `<h1>`, a page ends up with three or four competing “main topics”, and search engines have to guess which one describes the page. It also breaks the heading outline that screen-reader users rely on. Component libraries and AI-generated sections are the usual culprits — each card or hero brings its own `<h1>`.

## Symptoms

- Multiple `<h1>` elements across cards, heroes, or sections
- The heading outline has several top-level entries
- Reused components each declare their own `<h1>`

## How VibeCheck detects it

The `seo` detector flags this live in the browser and reports it to the widget's Problems list — and to your coding agent over MCP.

- **Issue string:** `Multiple <h1> headings`
- **Threshold:** more than one <h1> element

## Root causes

- Reusable section/card components each hard-code an `<h1>`
- A page composes several “hero” blocks that each own an `<h1>`
- Heading level was chosen for size, not hierarchy

## The fix

Keep a single `<h1>` for the page’s main topic and demote every other to `<h2>` or lower based on its place in the outline. Make reusable components accept a heading level (or default to `<h2>`) so they never emit a second `<h1>`.

### Steps

1. Find every `<h1>` on the page
2. Keep the one that names the page; change the rest to `<h2>`/`<h3>`
3. Parameterise shared components so they do not hard-code `<h1>`

```html
<h1>Product overview</h1>
<section>
  <h2>Pricing</h2>   <!-- was <h1> -->
</section>
<section>
  <h2>Features</h2>  <!-- was <h1> -->
</section>
```

## Framework-specific fixes

### React

Give reusable section components a level prop so only the page owns the `<h1>`. Next.js is identical.

```tsx
function Section({ as: Tag = 'h2', title }: { as?: 'h1' | 'h2' | 'h3'; title: string }) {
  return <Tag>{title}</Tag>
}
// page renders <h1> once; sections default to <h2>
```

### Vue

Use a dynamic component so the heading level is a prop, defaulting to h2.

```vue
<template>
  <component :is="as">{{ title }}</component>
</template>

<script setup lang="ts">
defineProps<{ as?: 'h1' | 'h2' | 'h3'; title: string }>()
</script>
```

### Svelte

Use `svelte:element` so the tag is data-driven, defaulting to h2.

```svelte
<script>
  export let as = 'h2'
  export let title
</script>

<svelte:element this={as}>{title}</svelte:element>
```

## FAQ

### Is multiple H1 actually penalised?

There is no direct penalty, but it weakens the topic signal and hurts accessibility. Keeping one `<h1>` is a clear, low-cost best practice.

### What about the HTML5 outline algorithm?

The sectioning-based outline algorithm was never implemented by browsers or assistive tech. In practice, use a single `<h1>` and ordered `<h2>`+ headings.

## Related problems

- [No H1 heading](https://vibecheck.wcgw.fun/fix/missing-h1-heading.md) — Search visibility
- [Missing <main> landmark](https://vibecheck.wcgw.fun/fix/missing-main-landmark.md) — AI readiness
- [Missing page title](https://vibecheck.wcgw.fun/fix/missing-page-title.md) — Search visibility
- [Images missing alt text](https://vibecheck.wcgw.fun/fix/missing-image-alt-text.md) — Search visibility

---

Fix guide from VibeCheck — https://vibecheck.wcgw.fun/fix/multiple-h1-headings. Full site index for LLMs: https://vibecheck.wcgw.fun/llms.txt
