Paragraphs & Headings
Headings and paragraphs turn a wall of text into a document. Assistive technologies offer “jump by heading” navigation; search engines use headings as strong topical signals; sighted users scan them as a visual outline. Misusing <h1>–<h6> for font size, or using bold instead of headings, breaks that outline.
This guide covers the heading outline, one-h1 guidance, skip links, heading versus bold, interaction with sectioning content, deep usage of p, br, hr, pre, and blockquote, line-breaking rules, and how to audit heading hierarchy for accessibility.
There are six ranks. Rank conveys hierarchy, not style. An h3 is a subsection of the preceding higher-rank heading in the flat outline used by browsers and AT today.
| 1 | <h1>Product documentation</h1> |
| 2 | <h2>Getting started</h2> |
| 3 | <h3>Install</h3> |
| 4 | <h3>Configure</h3> |
| 5 | <h2>API reference</h2> |
| 6 | <h3>Authentication</h3> |
| 7 | <h4>API keys</h4> |
| 8 | <h4>OAuth</h4> |
| 9 | <h3>Errors</h3> |
| 10 | <h2>Changelog</h2> |
| Rank | Typical role | Avoid |
|---|---|---|
| h1 | Page title / primary topic | Multiple competing page titles |
| h2 | Major sections | Skipping from h1 to h4 for style |
| h3–h4 | Subsections | Using for sidebar chrome size |
| h5–h6 | Deep nests (rare) | Over-nesting; reconsider IA |
best practice
HTML allows multiple h1 elements. Accessibility guidance and SEO practice still favor one primary h1 that names the page topic. Site logos in headers should not all be h1 on every page — use a paragraph, link, or styled text in the chrome, and reserve h1 for the page title in main.
| 1 | <!-- Recommended pattern --> |
| 2 | <header> |
| 3 | <p class="logo"><a href="/">ForgeLearn</a></p> |
| 4 | <nav>...</nav> |
| 5 | </header> |
| 6 | <main> |
| 7 | <h1>HTML Headings</h1> |
| 8 | ... |
| 9 | </main> |
| 10 | |
| 11 | <!-- Problematic: every card is an h1 --> |
| 12 | <section> |
| 13 | <h1>Card A</h1> |
| 14 | <h1>Card B</h1> |
| 15 | </section> |
note
info
Skip links let keyboard users bypass repeated chrome (logo, nav, search) and jump to main. They are usually visually hidden until focused.
| 1 | <body> |
| 2 | <a class="skip-link" href="#main">Skip to content</a> |
| 3 | <header>...</header> |
| 4 | <main id="main" tabindex="-1"> |
| 5 | <h1>Page title</h1> |
| 6 | </main> |
| 7 | </body> |
| 1 | .skip-link { |
| 2 | position: absolute; |
| 3 | left: -9999px; |
| 4 | top: 0; |
| 5 | z-index: 1000; |
| 6 | } |
| 7 | .skip-link:focus { |
| 8 | left: 12px; |
| 9 | top: 12px; |
| 10 | background: #00FF41; |
| 11 | color: #000; |
| 12 | padding: 8px 12px; |
| 13 | } |
best practice
<strong> and <b> emphasize or stylistically offset phrasing content. They do not create outline entries. If something is a section title, it must be a heading element — not bold text in a div.
| Markup | Outline? | Use for |
|---|---|---|
| h1–h6 | Yes | Section titles |
| strong | No | Important phrasing |
| b | No | Stylistic offset without emphasis |
| div.font-bold | No | Not a heading substitute |
| p + CSS size | No | Not a heading substitute |
| 1 | <!-- Wrong --> |
| 2 | <div class="title">Billing settings</div> |
| 3 | <p><b>Payment methods</b></p> |
| 4 | |
| 5 | <!-- Right --> |
| 6 | <h2>Billing settings</h2> |
| 7 | <h3>Payment methods</h3> |
danger
Sectioning content (section, article, nav, aside) groups thematic content. Each major section should generally start with a heading. The old HTML5 outline algorithm that computed ranks from nesting is not implemented for AT — author explicit ranks.
| 1 | <main> |
| 2 | <h1>Blog</h1> |
| 3 | <article> |
| 4 | <h2>Post title</h2> |
| 5 | <section> |
| 6 | <h3>Introduction</h3> |
| 7 | <p>...</p> |
| 8 | </section> |
| 9 | <section> |
| 10 | <h3>Details</h3> |
| 11 | <p>...</p> |
| 12 | </section> |
| 13 | </article> |
| 14 | <aside> |
| 15 | <h2>About the author</h2> |
| 16 | </aside> |
| 17 | </main> |
warning
<p> represents a paragraph. It cannot contain flow content like div, lists, or tables — the parser will close the paragraph early. Keep paragraphs for prose; use lists and divs for structure.
| 1 | <p>A short paragraph of prose.</p> |
| 2 | <p> |
| 3 | Paragraphs may contain |
| 4 | <a href="/x">links</a>, |
| 5 | <strong>importance</strong>, |
| 6 | and <code>code</code>. |
| 7 | </p> |
| 8 | |
| 9 | <!-- Broken intent --> |
| 10 | <p>Intro |
| 11 | <ul><li>A</li></ul> |
| 12 | </p> |
| 13 | <!-- Becomes: <p>Intro</p><ul>...</ul> --> |
info
<br> means a line break within text — addresses, poems, or line-oriented content — not vertical spacing between sections. <hr> is a thematic break between paragraph-level topics, not a decorative line (though it can be styled as one).
| 1 | <p> |
| 2 | Ada Lovelace<br /> |
| 3 | 123 Algorithm Way<br /> |
| 4 | London |
| 5 | </p> |
| 6 | |
| 7 | <section> |
| 8 | <p>End of chapter discussion.</p> |
| 9 | <hr /> |
| 10 | <p>Next topic begins here.</p> |
| 11 | </section> |
| 12 | |
| 13 | <!-- Bad: spacing with br --> |
| 14 | <p>Title</p> |
| 15 | <br /><br /><br /> |
| 16 | <p>Body</p> |
best practice
<pre> preserves whitespace and typically uses a monospace font via UA styles. Combine with <code> for code blocks. Avoid indenting pre contents in source if those spaces should not appear.
| 1 | <pre><code>function add(a, b) {{ |
| 2 | return a + b; |
| 3 | }}</code></pre> |
| 4 | |
| 5 | <!-- ASCII diagrams --> |
| 6 | <pre> |
| 7 | +-----+ +-----+ |
| 8 | | API | --> | DB | |
| 9 | +-----+ +-----+ |
| 10 | </pre> |
note
<blockquote> represents a quotation from another source. Use cite attribute for a URL and a <footer> or <cite> element for human-readable attribution. Do not use blockquote merely to indent text.
| 1 | <blockquote cite="https://html.spec.whatwg.org/"> |
| 2 | <p>Elements, attributes, and attribute values in HTML are defined |
| 3 | (by this specification) to have certain meanings (semantics).</p> |
| 4 | <footer>— <cite>HTML Living Standard</cite></footer> |
| 5 | </blockquote> |
| 6 | |
| 7 | <!-- Inline short quotes use q --> |
| 8 | <p>She said <q cite="https://example.com">ship it</q>.</p> |
Browsers break lines according to Unicode rules, CSS white-space, word-break, overflow-wrap, and language. Authors influence breaks with soft hyphens, word joiners, and <wbr>.
| Tool | Effect |
|---|---|
| <wbr> | Optional break opportunity |
| ­ (soft hyphen) | Break with hyphen when wrapped |
| white-space: nowrap | Prevent wrapping |
| overflow-wrap: anywhere | Allow aggressive breaks |
| <br> | Forced break |
| 1 | <p>https://example.com/very/<wbr />long/<wbr />path</p> |
| 2 | <p>Supercali­fragilistic</p> |
info
Audit headings regularly — especially after CMS or design-system changes.
| 1 | Manual: |
| 2 | 1. Turn off CSS — does the outline still make sense? |
| 3 | 2. Use a screen reader rotor / heading list |
| 4 | 3. Tab to skip link, then navigate by headings |
| 5 | |
| 6 | Automated: |
| 7 | - axe / Lighthouse: heading-order, empty headings |
| 8 | - HTML validator: empty headings, misplaced elements |
| 9 | - Custom script: document.querySelectorAll('h1,h2,h3,h4,h5,h6') |
| 10 | |
| 11 | Red flags: |
| 12 | - No h1 |
| 13 | - Skipped levels (h2 then h4) without reason |
| 14 | - Empty headings |
| 15 | - Headings outside main for page title |
| 16 | - Duplicate identical h2 spam for cards (consider h3 under a section h2) |
| 1 | [...document.querySelectorAll('h1,h2,h3,h4,h5,h6')] |
| 2 | .map((h) => `${h.tagName} ${h.textContent.trim().slice(0, 60)}`); |
best practice
| 1 | [ ] One primary h1 naming the page |
| 2 | [ ] No rank skips for styling |
| 3 | [ ] Sections/articles start with headings |
| 4 | [ ] Skip link to main |
| 5 | [ ] strong/b not used as fake headings |
| 6 | [ ] br not used for spacing |
| 7 | [ ] hr for thematic breaks only |
| 8 | [ ] blockquote for real quotations |
| 9 | [ ] pre/code for code samples with escaping |
| 10 | [ ] Outline audited with AT or tooling |
Card grids are a common source of bad outlines. If the page topic is “Documentation”, that is the h1. A section titled “Guides” is an h2. Each card title under Guides is usually an h3 (or a link inside a heading). Making every card an h2 flattens the outline into noise.
| 1 | <main> |
| 2 | <h1>Documentation</h1> |
| 3 | <section aria-labelledby="guides-heading"> |
| 4 | <h2 id="guides-heading">Guides</h2> |
| 5 | <ul class="card-grid"> |
| 6 | <li> |
| 7 | <h3><a href="/docs/html/forms">Forms</a></h3> |
| 8 | <p>Build accessible forms.</p> |
| 9 | </li> |
| 10 | <li> |
| 11 | <h3><a href="/docs/html/tables">Tables</a></h3> |
| 12 | <p>Tabular data patterns.</p> |
| 13 | </li> |
| 14 | </ul> |
| 15 | </section> |
| 16 | <section aria-labelledby="ref-heading"> |
| 17 | <h2 id="ref-heading">Reference</h2> |
| 18 | ... |
| 19 | </section> |
| 20 | </main> |
info
When a paragraph or heading switches language, set lang on that element. Bidirectional quotations may need dir. Line breaking and hyphenation dictionaries follow language tags.
| 1 | <h2>Guest essay</h2> |
| 2 | <p>The author writes:</p> |
| 3 | <blockquote lang="de" cite="https://example.com/de"> |
| 4 | <p>Semantik ist keine optionalität.</p> |
| 5 | </blockquote> |
| 6 | <p>Back to English analysis…</p> |
A small document showing skip-friendly structure, thematic break, and a quotation.
Markdown # headings become h1 by default in many pipelines — disastrous when the page template already provides an h1 from the title field. Configure the renderer to start at h2, or demote programmatically. WYSIWYG editors often inject <br> for Enter and bold for “titles”; train authors and sanitize output.
| 1 | // Demote markdown headings one level when page already has h1 |
| 2 | function demoteHeadings(html) { |
| 3 | return html.replace(/<(\/?)h([1-5])\b/gi, (_, slash, n) => |
| 4 | `<${slash}h${Number(n) + 1}` |
| 5 | ); |
| 6 | } |
pro tip
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.