Rule handbook metadata

Rule
DET.LANDMARKS.REQUIRED · lane deterministic
Page status
current
page_version
c29f7f449fb7047c691e49d9eca95939fbfd12f1de92145468641f7c5150f192
generated_at
2026-05-25T12:00:00.000Z
registry_fingerprint
2ce40848effce579d3e4879f6ca85535183a14db201870a6c007da424624550c

How this rule is fixed

Pilot deterministic fixer: handbook_after · harness mode standalone · verify expect_rule_clean.

  • Harness: apply-harness-fixture-remediation.py copies the After example HTML from this handbook page onto the defect fixture, then invoke-det-ruleset-remediation-verify.sh re-audits and expects zero findings for this rule.
  • Production sites: run-website-ux-remediation-loop.sh runs handbook_html_patch (HTML patches in lib/ux-deterministic-fixers/fixers/patches/) before invoking the Cursor agent when the quality gate still fails.

Detection module: docs/design/ux-audit/deterministic-design-rules.md#det-landmarks-required. Scroll down for Before / After examples and Evidence and remediation steps.

Purpose

Screen readers, keyboard users, and skip-link targets rely on document landmarks to jump directly to the primary content column, global navigation, and site chrome. Kitchen Sink handbook shells (showcase_page, handbook_page), marketing layouts (landing_page, marketing_page), and app shells emit predictable anatomy: header.site-header or header.landing-header, aside.forge-sidebar with section rails, main#main, and closing footer / .ks-site-footer-region (Ksf).

This deterministic rule samples the live DOM during the metrics phase. It counts visible, non-aria-hidden landmarks at document level (not nested inside main, article, aside, or section). A page passes when:

  • Exactly one main or [role="main"] wraps the primary reading column.
  • When ≥ 2 non-auxiliary navigational links sit outside main (in header, nav, aside, or sidebar chrome), at least one nav or [role="navigation"] landmark exists outside main.
  • When KS header chrome is detected (.site-header, .landing-header, header.site-header, [data-shell-region="header|banner"]), a single document-level header or [role="banner"] is present.
  • When KS footer chrome is detected (footer, .ks-site-footer-region, .fs-footer, [data-shell-region="footer"]) outside main, a document-level footer or [role="contentinfo"] is present.
  • No duplicate document-level banner or contentinfo landmarks.

Plan: Map layout contracts to landmark roles before shipping HTML. Do: Use semantic elements KS layouts already name in Python (layouts.py, components.py). Check: landmarksReport.violations is empty. Adjust: Replace styled div shells with header / main / nav / footer; demote extra main or nested section headers that duplicate banner/contentinfo.

Passing signals

  • main id="main" with class doc-main, fs-landing-main, or app workspace class wraps the primary content column — one visible root only.
  • header.site-header (handbook/showcase masthead) or header.landing-header with nav.landing-header-navbar / nav.landing-nav provides the document banner and, when global links exist, the navigation landmark outside main.
  • aside.forge-sidebar contains nav.nav-scroll with aria-label="Sections" or "Primary navigation" — sidebar wayfinding counts as a nav landmark outside main.
  • footer.ks-site-footer-region (Ksf) or footer.fs-footer outside main exposes contentinfo when footer chrome is contracted at page scope.
  • In-page nav.forge-toc (on-this-page TOC) and section header elements inside main do not inflate top-level banner counts — only document-level chrome is measured.
  • Skip links, #main hash targets, and auxiliary anchors (Skip to content, #top) are excluded from the nav-link threshold.

Failing signals

  • missing-main: Primary content sits in div.doc-main, article, or a Bootstrap column with no main / [role="main"] landmark (main_landmark_count=0).
  • duplicate-main: Two or more document-level main landmarks — for example a workspace main plus a marketing band also tagged main.
  • missing-nav: header.site-header or header chrome exposes ≥ 2 global links (Overview, Docs, Trust, …) but links are bare anchors with no wrapping nav outside main (nav_applicable=true, nav_landmark_count=0).
  • missing-banner: Visible .site-header or .landing-header exists outside main but the root is a div instead of header / [role="banner"].
  • missing-contentinfo: .ks-site-footer-region or footer chrome sits outside main as a div with no nested or sibling footer / [role="contentinfo"] at document level.
  • duplicate-banner / duplicate-contentinfo: Multiple top-level header or footer elements outside nested sections — common when a page-level masthead and a hero header both register as banners.

Before example

Before (failing example)

Body

Primary column is a div — no main landmark; header and footer are not semantic landmarks.

Failing KS markup: handbook-style shell uses styled div chrome and a content column without main; global header links lack a nav landmark; footer band is a div outside the content column.

After example

After (passing example)

Body

One main landmark; sidebar nav and header/footer chrome are exposed to assistive tech.

© Forge

Passing KS markup: same handbook anatomy with semantic landmarks — header.site-header, nav in the sidebar rail, main#main.doc-main, and footer.ks-site-footer-region (Ksf) outside the content column.

Evidence and remediation

Evidence: Metrics phase emits landmarksReport with counts (mainCount, navApplicable, navLandmarkCount, topBannerCount, topContentinfoCount, outsideMainNavLinkCount) and violations[] (kind: missing-main, duplicate-main, missing-nav, missing-banner, duplicate-banner, missing-contentinfo, duplicate-contentinfo). Findings use area accessibility, severity minor, and evidence such as main_landmark_count=0 or chrome_header_without_banner=true. Capture the accessibility tree or a landmark overlay screenshot for the audited URL.

Remediate (in order):

  1. Establish one main — wrap the primary reading/workspace column in <main id="main"> (or a single [role="main"]); remove duplicate mains from hero bands or nested layouts.
  2. Landmark the global nav — when ≥ 2 wayfinding links live outside main, wrap them in <nav aria-label="…"> in header.landing-header, aside.forge-sidebar, or a dedicated shell rail; do not rely on bare anchor lists in header chrome.
  3. Use semantic header/footer shells — change .site-header / .landing-header roots from div to <header>; expose Ksf footer chrome as <footer class="ks-site-footer-region"> or nest <footer> inside the contracted footer region when the outer wrapper must stay a div for hash attrs.
  4. Demote nested headers — section titles inside main may use <header> only when they will not register as a second document-level banner; prefer heading elements inside section for in-page bands.
  5. Re-audit — run analyze-website-ux.mjs on handbook, marketing, and app URLs; confirm landmarksReport.violations is empty and skip-link #main resolves to the sole main landmark.