Skip to main content

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

  1. Fetch the page HTML with a standard GET request (respect robots rules). Use a configurable timeout (e.g., 10s) and parse the raw HTML response.
  2. Parse the HTML using the cheerio library (Node.js). Do not execute JavaScript — only evaluate the static HTML returned by the server. Use cheerio as the single, recommended parser for this audit to avoid mixing parsing approaches.
  3. 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.
  4. 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 of role attributes.
    • 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 example div, p, section, article, header, footer, nav, ul, ol, table, h1h6, 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.
  1. 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.
  2. 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 main and rely on ARIA roles; treat ARIA roles as acceptable fallbacks but surface them in diagnostics.

What to store

FieldTypeComment
urlTEXTThe page URL where the check ran.
http_statusINTEGERHTTP status code returned when fetching the page.
tag_nameTEXTSemantic tag inspected (e.g., main, nav, header).
presentBOOLEANTrue if the tag was present in the static HTML.
countINTEGERNumber of occurrences of the tag on the page.
text_lengthINTEGERTrimmed text length inside the element (bytes).
has_roleBOOLEANTrue if an ARIA role equivalent exists for this landmark.
example_snippetTEXTShort HTML/text snippet illustrating the detected element or failing area.
is_problemBOOLEANTrue when the rule marks the element as a problem per triggers below.
noteTEXTAny crawler notes (e.g., ARIA_used_instead_of_tag, SPA_may_require_render).
block_in_inline BOOLEANBOOLEANTrue if a block-level element was detected inside an inline element on the page.
violation_detail TEXTTEXTShort description of the nesting violation and sample HTML snippet.
discovered_onTIMESTAMPCrawl timestamp when the page was inspected.

Condition for trigger

  • Trigger Rule 1: Missing main landmark

    • 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 main landmark; add a single <main> or role="main" to identify primary content."
  • Trigger Rule 2: Multiple main landmarks

    • 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."
  • 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/footer landmark; consider adding semantic elements or ARIA roles for assistive technologies."
  • 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."
  • 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."

Sources

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

  1. Add a single <main> element that contains the page's primary content; do not include multiple <main> elements.
  2. Use <nav>, <header>, <footer>, <article>, <section>, and <aside> where semantically appropriate; ensure these elements contain meaningful content and headings.
  3. If using server-side templates, ensure templating does not accidentally duplicate <main> or move primary content outside of it.
  4. When tags cannot be used (legacy templates or constraints), add ARIA roles (role="main", role="navigation") to the appropriate containers as an accessible fallback.
  5. 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.