Rule handbook metadata
- Rule
DET.LANDMARKS.REQUIRED· lanedeterministic- 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.pycopies the After example HTML from this handbook page onto the defect fixture, theninvoke-det-ruleset-remediation-verify.shre-audits and expects zero findings for this rule. - Production sites:
run-website-ux-remediation-loop.shrunshandbook_html_patch(HTML patches inlib/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
mainor[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 onenavor[role="navigation"]landmark exists outsidemain. - When KS header chrome is detected (
.site-header,.landing-header,header.site-header,[data-shell-region="header|banner"]), a single document-levelheaderor[role="banner"]is present. - When KS footer chrome is detected (
footer,.ks-site-footer-region,.fs-footer,[data-shell-region="footer"]) outsidemain, a document-levelfooteror[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 classdoc-main,fs-landing-main, or app workspace class wraps the primary content column — one visible root only.header.site-header(handbook/showcase masthead) orheader.landing-headerwithnav.landing-header-navbar/nav.landing-navprovides the document banner and, when global links exist, the navigation landmark outsidemain.aside.forge-sidebarcontainsnav.nav-scrollwitharia-label="Sections"or"Primary navigation"— sidebar wayfinding counts as a nav landmark outsidemain.footer.ks-site-footer-region(Ksf) orfooter.fs-footeroutsidemainexposes contentinfo when footer chrome is contracted at page scope.- In-page
nav.forge-toc(on-this-page TOC) and sectionheaderelements insidemaindo not inflate top-level banner counts — only document-level chrome is measured. - Skip links,
#mainhash targets, and auxiliary anchors (Skip to content,#top) are excluded from the nav-link threshold.
Failing signals
missing-main: Primary content sits indiv.doc-main,article, or a Bootstrap column with nomain/[role="main"]landmark (main_landmark_count=0).duplicate-main: Two or more document-levelmainlandmarks — for example a workspacemainplus a marketing band also taggedmain.missing-nav:header.site-headeror header chrome exposes ≥ 2 global links (Overview,Docs,Trust, …) but links are bare anchors with no wrappingnavoutsidemain(nav_applicable=true,nav_landmark_count=0).missing-banner: Visible.site-headeror.landing-headerexists outsidemainbut the root is adivinstead ofheader/[role="banner"].missing-contentinfo:.ks-site-footer-regionor footer chrome sits outsidemainas adivwith no nested or siblingfooter/[role="contentinfo"]at document level.duplicate-banner/duplicate-contentinfo: Multiple top-levelheaderorfooterelements outside nested sections — common when a page-level masthead and a heroheaderboth register as banners.
Before example
Before (failing example)
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)
Forge
Handbook
Chapter
Body
One main landmark; sidebar nav and header/footer chrome are exposed to assistive tech.
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):
- 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. - Landmark the global nav — when ≥ 2 wayfinding links live outside
main, wrap them in<nav aria-label="…">inheader.landing-header,aside.forge-sidebar, or a dedicated shell rail; do not rely on bare anchor lists in header chrome. - Use semantic header/footer shells — change
.site-header/.landing-headerroots fromdivto<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 adivfor hash attrs. - Demote nested headers — section titles inside
mainmay use<header>only when they will not register as a second document-level banner; prefer heading elements insidesectionfor in-page bands. - Re-audit — run
analyze-website-ux.mjson handbook, marketing, and app URLs; confirmlandmarksReport.violationsis empty and skip-link#mainresolves to the sole main landmark.