Semantic HTML5 elements presence
Issue No: 141
Category: Semantic HTML
Issue type: Issue
Priority: IMPORTANT
Description
Verifies that pages include and correctly use core HTML5 semantic landmark elements: <main>, <article>, <section>, <nav>, <header>, <footer>, and <aside>. The audit flags missing landmarks, empty landmark containers, and non-semantic wrappers used without ARIA landmarks.
How do we capture it
- Fetch the page HTML with a standard
GETrequest (respect robots rules). Use a configurable timeout (e.g., 10s) and parse the raw HTML response. - Parse the HTML using the
cheeriolibrary (Node.js). Do not execute JavaScript — only evaluate the static HTML returned by the server. Usecheerioas the single, recommended parser for this audit to avoid mixing parsing approaches. - Targeted elements to detect and validate:
<main>— there should be one primary landmark per document; detect presence and count.<nav>— detect site or page navigation landmarks; capture counts and whether multiple navs appear without labels.<header>and<footer>— detect page-level headers/footers and whether they are meaningful (contain links/headings).<article>and<section>— detect content grouping elements and ensure they contain heading-level text (h1–h6) or descriptive content.<aside>— detect complementary content such as sidebars or related links.
- Validation heuristics and checks:
- Presence: record whether each target tag exists and its element count.
- Uniqueness: flag if more than one
<main>is present or zero<main>elements are present. - Emptiness: for each detected semantic element, compute trimmed text length; flag if effectively empty (e.g., text length < 20 bytes and no meaningful child headings/links).
- Non-semantic substitutes: detect common non-semantic wrappers (e.g.,
<div class="header">,<div id="main">) that lack ARIA roles; if found, capture the absence ofroleattributes. - ARIA fallbacks: detect
role="main",role="navigation", etc., and treat them as valid fallbacks when tags are absent.
- Block/inline validation: detect illegal nesting where any inline element contains block-level descendants. Maintain canonical lists of inline elements (for example
a,span,strong,em,img,code,b,i,small,label) and block-level elements (for examplediv,p,section,article,header,footer,nav,ul,ol,table,h1–h6,main,aside,figure). Flag inline elements that contain one or more block-level descendants as markup errors. Inline elements are allowed inside block-level elements and should not be flagged when used correctly.
- Record referrer context: store the page URL, status code, and the DOM path or a short sample of content for failing elements to help locate the problem in the page.
- Edge cases & constraints:
- Do NOT execute JavaScript; any elements injected only at runtime will not be discovered by this static check.
- Single-page applications that hydrate client-side may require a rendering-capable check (headless browser) to validate final DOM state.
- Some pages intentionally omit
mainand rely on ARIA roles; treat ARIA roles as acceptable fallbacks but surface them in diagnostics.
What to store
| Field | Type | Comment |
|---|---|---|
| url | TEXT | The page URL where the check ran. |
| http_status | INTEGER | HTTP status code returned when fetching the page. |
| tag_name | TEXT | Semantic tag inspected (e.g., main, nav, header). |
| present | BOOLEAN | True if the tag was present in the static HTML. |
| count | INTEGER | Number of occurrences of the tag on the page. |
| text_length | INTEGER | Trimmed text length inside the element (bytes). |
| has_role | BOOLEAN | True if an ARIA role equivalent exists for this landmark. |
| example_snippet | TEXT | Short HTML/text snippet illustrating the detected element or failing area. |
| is_problem | BOOLEAN | True when the rule marks the element as a problem per triggers below. |
| note | TEXT | Any crawler notes (e.g., ARIA_used_instead_of_tag, SPA_may_require_render). |
| block_in_inline BOOLEAN | BOOLEAN | True if a block-level element was detected inside an inline element on the page. |
| violation_detail TEXT | TEXT | Short description of the nesting violation and sample HTML snippet. |
| discovered_on | TIMESTAMP | Crawl timestamp when the page was inspected. |
Condition for trigger
-
Trigger Rule 1: Missing
mainlandmark- Target Field:
tag_name,count - Evaluation Logic:
tag_name = 'main' AND count = 0 AND has_role = false - Severity: CRITICAL
- Diagnostic Message: "Page is missing a primary
mainlandmark; add a single<main>orrole="main"to identify primary content."
- Target Field:
-
Trigger Rule 2: Multiple
mainlandmarks- Target Field:
tag_name,count - Evaluation Logic:
tag_name = 'main' AND count > 1 - Severity: WARNING
- Diagnostic Message: "Multiple
<main>elements found; ensure only one primary landmark per page."
- Target Field:
-
Trigger Rule 3: Missing navigational or page-level landmarks
- Target Field:
tag_name,count,has_role - Evaluation Logic:
tag_name IN ('nav','header','footer') AND count = 0 AND has_role = false - Severity: WARNING
- Diagnostic Message: "Page lacks a
nav/header/footerlandmark; consider adding semantic elements or ARIA roles for assistive technologies."
- Target Field:
-
Trigger Rule 4: Semantic element present but empty
- Target Field:
text_length - Evaluation Logic:
present = true AND text_length < 20 - Severity: WARNING
- Diagnostic Message: "Semantic element
<[tag_name]>appears present but contains little or no content; verify correct nesting and that content is not rendered only by client-side scripts."
- Target Field:
-
Trigger Rule 5: Non-semantic wrapper without ARIA role
- Target Field:
tag_name,has_role,note - Evaluation Logic:
tag_name = 'div' AND has_role = false AND note LIKE '%class="header"%|%id="main"%' - Severity: SUGGESTION
- Diagnostic Message: "Non-semantic
<div>appears to act as a landmark (e.g.,class="header"); prefer semantic elements or add appropriate ARIA roles."
- Target Field:
Sources
- WHATWG HTML Living Standard — Sections on landmarks: https://html.spec.whatwg.org/multipage/sections.html
Long description
Semantic HTML5 landmarks help browsers, search engines, and assistive technologies understand page structure. A clear primary content region (<main>) and appropriate landmarks for navigation and complementary content (<nav>, <aside>, <header>, <footer>) improve accessibility, keyboard navigation, and can aid automated tools that extract page structure for indexing or previews.
Common failure scenarios checked by the crawler:
- Missing
<main>or reliance solely on non-semantic wrapper<div id="main">with no ARIA role. - Multiple
<main>elements due to templating errors or nested components. - Landmarks present but empty because main content is injected client-side after an initial server-side shell.
- Use of semantic tags in non-standard ways (e.g.,
<header>used repeatedly for small layout fragments) which can confuse assistive tech if headings are not present.
Note about the sample script: a small Node.js checker using axios (HTTP fetching) and cheerio (HTML parsing) — the exact libraries to use for this audit — is sufficient for the static detection described above. Install with:
npm install axios cheerio
For pages where the DOM is built client-side, add a rendering-capable step (headless browser snapshot) to validate final DOM states.
How to Fix
- Add a single
<main>element that contains the page's primary content; do not include multiple<main>elements. - Use
<nav>,<header>,<footer>,<article>,<section>, and<aside>where semantically appropriate; ensure these elements contain meaningful content and headings. - If using server-side templates, ensure templating does not accidentally duplicate
<main>or move primary content outside of it. - When tags cannot be used (legacy templates or constraints), add ARIA roles (
role="main",role="navigation") to the appropriate containers as an accessible fallback. - For single-page apps where content is populated client-side, add a rendering-capable validation step (headless browser) in CI to verify final DOM landmarks.