Rule handbook metadata
- Rule
DET.NAV.FOCUS_ORDER· lanedeterministic- Page status
- current
- page_version
84636f49a8af2c9595b613d075ec4f5c84d25ca7537f8b8fc84b43684a3d0210- generated_at
- 2026-05-25T21:15: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-nav-focus_order. Scroll down for Before / After examples and Evidence and remediation steps.
Purpose
Keyboard users rely on Tab and Shift+Tab to move through interactive controls in an order that matches what sighted readers scan: header chrome → primary column → complementary rails → footer. When DOM order, CSS layout (position, float, flex-direction, grid placement), or tabindex overrides diverge from visual placement, focus jumps unpredictably—skipping masthead actions, revisiting regions, or trapping users in chrome that looks finished.
Kitchen Sink shells (header.site-header, nav.fs-primary-nav-global / Kpn, aside.forge-sidebar / Ksr, main#main, footer.ks-site-footer-region / Ksf) already encode a landmark sequence. This deterministic rule runs during the metrics phase. Playwright collects up to 48 visible tab stops per sampled page, ranks them (positive tabindex first, then DOM order), and compares consecutive stop centers against thresholds from det-nav-focus-order.check.js:
| Violation kind | Trigger |
|---|---|
positive-tabindex |
Any focusable control has tabindex > 0 |
reverse-vertical |
Next stop center is > 72px above the previous stop |
reverse-horizontal (LTR) |
On the same row (≤ 40px vertical band), next stop is > 96px left of the previous |
handbook-chrome-tab-suppressed |
Curated handbook top nav is present and > 16 sidebar / in-page ToC / breadcrumb links use tabindex="-1" (detected via .fleet-handbook-topnav and complementary selectors) |
Regions are classified as header, nav, main, aside, footer, or document for evidence strings.
Plan: Walk keyboard paths on handbook, marketing, and showcase layouts before shipping HTML. Do: Keep focusable elements in DOM order that mirrors visual reading order; reserve tabindex="-1" for programmatic focus targets only. Check: navFocusOrderReport.violations is empty. Adjust: Remove positive tabindex, reorder DOM or fix layout, and restore natural tab order for complementary nav when top nav does not duplicate those destinations.
Passing signals
- Tab moves through
header.site-header/header.landing-headerskip link →nav.fs-primary-nav-global(Kpn) ornav.landing-navlinks left-to-right, then intomain#mainwithout large upward jumps. fs-nav-dropdown__triggerand.fs-nav-dropdown__linkpanel items follow DOM order matching their visual row; noflex-row-reverseor absolute positioning that inverts LTR toolbar order.- Deep handbook links in
aside.forge-sidebar(Ksr,#ks-sidebar-aside) andaside.ks-doc-toc-rail(Ktx) appear in tab order where keyboard users expect them relative tomain—typically after masthead nav, alongside or before the primary column depending on layout grid. - Hero CTAs (
.btn.btn-forge,.landing-nav-cta) use defaulttabindex(0 or implicit); notabindex="1"/tabindex="5"shortcuts. footer.ks-site-footer-region(Ksf) links receive focus aftermaincontent on the sampled path.- When complementary chrome links are removed from tab order (
tabindex="-1"), curated top nav still exposes every in-page section—or suppressed count stays ≤ 16. - Evidence shape:
navFocusOrderReport.tabStopCount> 0 andviolations[]empty on audited URLs.
Failing signals
positive-tabindex— abutton,a, orinputinmain, hero, or sticky CTA band usestabindex="2"or higher; Tab visits that control before earlier masthead links (tabindex=Nin evidence).reverse-vertical— after tabbing throughmainbody links, focus jumps to aposition-fixedmasthead action or sidebar rail > 72px higher on screen (deltaYnegative,fromRegion=main,toRegion=header|aside).reverse-horizontal—nav.fs-primary-nav-global-innerusesflex-row-reverse, CSS gridorder, or float layout so Tab moves right-to-left within a toolbar row (deltaXnegative, same-row band).handbook-chrome-tab-suppressed— handbook shell with curated top nav hides > 16 visibleaside.forge-sidebar,.ks-doc-toc-rail, or breadcrumb anchors from tab order while top nav omits those destinations.- Co-failures: inverted nav after flattening flyouts (
DET.NAV.DEPTH); missingnavlandmark wrapping reordered link bands (DET.LANDMARKS.REQUIRED). - Evidence shapes:
step=N deltaY=-120px from <a>#cta (main) to <a>#skip (header);tabindex=3 <button> id="hero-cta" region=main;handbook_chrome_tab_suppressed count=18 max=16.
Before example
Before (failing example)
Forge
Governed delivery
Keyboard tab order diverges from visual LTR nav and positive tabindex on the CTA.
QuickstartFailing KS markup: Kpn uses flex-row-reverse so Tab traverses Trust → Docs → Overview right-to-left on one row (reverse-horizontal). Hero Quickstart uses tabindex="3" (positive-tabindex).
After example
After (passing example)
Forge
Governed delivery
Tab moves skip link → Kpn left-to-right → main content without tabindex overrides.
QuickstartPassing KS markup: Kpn links follow DOM order left-to-right; hero CTA uses default tab order; skip link and masthead precede main in both visual and focus sequence.
Evidence and remediation
Evidence: Playwright collectNavFocusOrderReport → metrics.navFocusOrderReport with tabStopCount, rtl, violations[] (kind, step, deltaY, deltaX, tabindex, fromRegion, toRegion, fromId, toId), and handbookFocus (hasTopnav, suppressedChromeTabCount). Findings cite step=N deltaY=…, tabindex=N, or handbook_chrome_tab_suppressed count=….
Remediate (in order):
- Remove positive tabindex — delete
tabindex="1"and higher on CTAs, skip links, and dropdown triggers; use DOM order and:focus-visiblestyles instead. - Align DOM with layout — move
position-fixedor visually elevated controls into the landmark that matches their screen position (mastheadheader.site-header, not aftermain); avoidflex-row-reverseonfs-primary-nav-global-innerand toolbars unlessdir="rtl". - Fix vertical inversions — reorder sidebar (Ksr), in-page ToC (Ktx), and
maincolumns so Tab does not jump upward more than 72px between consecutive stops; check sticky headers and offcanvas (Kco) insertion points. - Restore complementary nav tab order — when using
tabindex="-1"on sidebar or.ks-doc-toc-raillinks, ensure curated top nav lists every section or keep suppressed link count ≤ 16. - Re-verify after nav refactors — flattening flyouts (
DET.NAV.DEPTH) and deduplicating chrome (DET.NAV.DEDUP) can change tab paths; re-run keyboard smoke tests throughfs-nav-dropdown__triggerpanels. - Re-run
analyze-website-ux.mjs; for harness fixtures useauditor-tests/invoke-det-ruleset-harness.sh --only-rule DET.NAV.FOCUS_ORDERandnode --test auditor-tests/det-nav-focus-order.test.js.