Rule handbook metadata
- Rule
DET.APP.FOCUS_TRAP· lanedeterministic- Page status
- stale
- page_version
b00d291ded12c54043f412b186dfb0ed74880144c3ffab69d19386d2c6be1b47- generated_at
- 2026-05-19T20:15:00.000Z
- registry_fingerprint
6773fda516344e110b5a7b1435e655e1264e773825ca8bbe62194189891c42ba
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-app-focus-trap. Scroll down for Before / After examples and Evidence and remediation steps.
Purpose
Kitchen Sink handbook and product layouts rely on overlay shells that block the page until the user dismisses them:
- Bootstrap —
.modaland.offcanvas(mobile handbook navdocNavOffcanvas,Kco). - KS diagram lightbox —
#diagramModalwith.diagram-modal-backdrop,.diagram-modal-close, andcloseDiagramModal()(forge-theme.js). - Topic preview —
#topicPreviewModalwith.topic-preview-modal-backdrop,[data-topic-preview-close], andopenTopicPreviewModal()/closeTopicPreviewModal(). - Custom dialogs —
[role="dialog"]surfaces witharia-modal="true".
While an overlay is open, keyboard users must not tab into background <main> content, lose focus outside the shell, or be stuck without a dismiss control. This deterministic rule scans overlay markup on every crawled page and, when a safe opener exists, runs a short Tab-cycle probe (up to two shells per page).
Plan: Inventory .modal, .offcanvas, #diagramModal, and #topicPreviewModal on app/handbook templates. Do: Pair shells with visible openers, Bootstrap dismiss wiring, and KS close affordances. Check: Open the overlay with keyboard, Tab through controls, confirm focus never lands on main links. Adjust: Add aria-modal="true", inert/aria-hidden on the page root, or Bootstrap's built-in trap when custom JS bypasses it.
Passing signals
- Every overlay shell includes a keyboard-reachable dismiss:
data-bs-dismiss,.btn-close,.diagram-modal-close,[data-topic-preview-close], or a<button>whosearia-label/ text contains "close". - Bootstrap
.offcanvas/.modalshells with anidhave a visible opener usingdata-bs-toggle+data-bs-target(oraria-controls) — e.g. the fixedbtn btn-forgecontrol targeting#docNavOffcanvas. - With the shell open (
show/active/aria-hidden="false"), repeated Tab cycles keepdocument.activeElementinside the shell;mainlinks are not focusable until dismiss. - Open
role="dialog"overlays exposearia-modal="true"plusaria-labelledbyoraria-label(topic preview sets these inensureTopicPreviewModal()). - KS diagram modal uses the shared fragment:
#diagramModal+.diagram-modal-closecallingcloseDiagramModal(); closed state uses thehiddenattribute so scaffold text stays out of the tab order.
Failing signals
no-dismiss— Shell present (.modal,.offcanvas, or[role="dialog"]) with no dismiss control the probe recognizes.no-trigger— Bootstrap shell has anidbut no visibledata-bs-target/aria-controlsopener in the document (orphan offcanvas/modal).trap-escape— While open, Tab moves focus to an element outside the shell (e.g. a.nav-linkin the page chrome).background-tabbable— Focus can land on the first tabbable control inside<main>while the overlay is open.no-focusable— Open shell exposes no tabbable controls (keyboard users cannot reach actions or close).missing-aria-modal— Open dialog overlay hasrole="dialog"but notaria-modal="true".
Before example
Before (failing example)
Failing KS markup: diagram lightbox open without a close control; background prose remains in the tab order.
After example
After (passing example)
Passing KS markup: canonical diagram modal fragment plus handbook offcanvas with Bootstrap dismiss and a paired mobile opener.
Evidence and remediation
Evidence: Metrics field appFocusTrapReport with overlayShellCount, openTestsRun, and violations[] entries tagged no-dismiss, no-trigger, trap-escape, background-tabbable, no-focusable, or missing-aria-modal. Findings cite shell id, truncated className, and the element that received focus when escape was detected.
Remediate (in order):
- Add a visible, labeled close —
.btn-close+data-bs-dismiss="offcanvas"for Bootstrap shells;.diagram-modal-closeor[data-topic-preview-close]for KS modals. - Pair Bootstrap shells with
data-bs-toggle/data-bs-targeton the handbook hamburger (components/layouts.pymobile nav pattern). - When custom JS opens overlays, mirror
forgeApplyDiagramModalOpen/closeDiagramModalor topic-preview helpers sohidden/aria-hidden/activestay consistent and background content is inert or aria-hidden. - On open, verify Tab cycles stay inside the shell; if focus escapes, enable Bootstrap's modal/offcanvas focus trap or set
inertonmainuntil dismiss. - Set
role="dialog"witharia-modal="true"and restore focus to the triggering control on close (DET.NAV.FOCUS_ORDER). - Re-run
analyze-website-ux.mjson handbook and showcase URLs that include diagram expand and mobile nav.