Rule handbook metadata
- Rule
DET.CTA.HIERARCHY· lanedeterministic- Page status
- current
- page_version
8790401c69d14712da45308df1105ae0d4fa1a29997485bbdebc1d9def6e474f- generated_at
- 2026-05-19T22: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-cta-hierarchy. Scroll down for Before / After examples and Evidence and remediation steps.
Purpose
Kitchen Sink landing and product heroes (components/components.py, css/forgesdlc-theme.css) render conversion actions in landing-hero-actions with a landing-hero-actions__buttons row. The canonical pattern is one dominant btn btn-forge primary, an optional btn btn-cyan-outline secondary, and muted landing-hero-secondary-link text links below. When two or more filled primaries share the same hero, modal, or sticky footer band, scanners cannot tell which action the page wants them to take.
This deterministic rule scans logical viewport regions and counts primary action controls—<a>, <button>, <input type="button|submit|reset">, and [role="button"] whose classes include btn-forge or btn-primary. Outline and low-emphasis styles (btn-forge-outline, btn-cyan-outline, btn-outline, btn-secondary, btn-link) are not primaries. The cap is one primary per region (MAX_PRIMARY_CTAS_PER_REGION = 1). Regions include:
| Region | Typical KS selectors |
|---|---|
| Hero | .landing-hero-wide, .landing-hero-grid-wrap, .landing-hero, .product-hero, main .hero |
| Modal / panel | .modal.show, .offcanvas.show, #diagramModal, #topicPreviewModal, [role="dialog"][aria-modal="true"] |
| Sticky footer | .fixed-bottom, .sticky-bottom, [class*="sticky-footer"], [class*="sticky-cta"], fixed/sticky bands pinned to the viewport bottom |
Navigation, pagination, breadcrumbs, dropdown menus, tab lists, and cookie banners are excluded from region scans.
Plan: Inventory hero, modal, and sticky-footer bands for duplicate btn-forge / btn-primary siblings. Do: Keep one filled primary; demote extras to btn-cyan-outline, landing-hero-secondary-link, or a disclosure. Check: Confirm metrics.ctaHierarchyReport has no violations. Adjust: When marketing adds a second primary, move the alternate path below the fold or into secondary links instead of matching visual weight.
Passing signals
landing-hero-actions__buttonscontains exactly one visiblebtn btn-forge(orbtn-primary) plus optionalbtn btn-cyan-outlineorbtn btn-forge-outlinesecondaries.- Additional destinations use
landing-hero-secondary-linkswithlanding-hero-secondary-linkanchors—not counted as primaries. - Open modals and offcanvas panels expose one filled primary in the footer or body action row; cancel/close uses outline or link styling.
- Sticky footer bands (
.fixed-bottom,[class*="sticky-cta"]) show one filled primary; companion actions use outline or text links. metrics.ctaHierarchyReportreportsmaxAllowed: 1,violations: [], and scanned regions withprimaryCount ≤ 1.
Failing signals
landing-hero-actions__buttonsshows two or more siblingbtn-forgeorbtn-primarylinks (auditor reportsprimary_ctas=2or higher in the hero region, major severity; critical when count exceedsmaxAllowed + 1).- Product heroes (
.product-hero) stack multiple filled primaries—e.g. "Start quickstart" and "Book demo" both onbtn-forge—with no outline demotion. - An open
.modal.showor#diagramModalfooter renders two filled submit/confirm buttons. - A sticky footer band (
.fixed-bottom,[class*="sticky-footer"]) exposes two filled primaries such as "Subscribe" and "Contact sales". - Evidence strings include
region=hero primary_ctas=N max=1,selectorHintpointing atdiv.landing-hero…, andlabels="Get started | Start trial | …". - Page may pass
DET.BUTTON.GROUP.MAX(≤ 3 horizontal actions) yet still fail here because primary class role, not total button count, is the gate.
Before example
Before (failing example)
Governed human + agent delivery
One methodology spine from intent to evidence.
Failing KS markup: two filled btn-forge primaries in the same landing-hero region—competing conversion paths at equal weight.
After example
After (passing example)
Governed human + agent delivery
One methodology spine from intent to evidence.
Passing KS markup: one btn-forge primary, one outline secondary, and muted text links for alternate paths—the canonical KS hero CTA stack.
Evidence and remediation
Evidence: Auditor field metrics.ctaHierarchyReport with maxAllowed: 1 and violations[] entries (kind: too-many-primary-ctas, region, primaryCount, selectorHint, labels). Findings cite region=hero|modal|stickyFooter, primary_ctas=N max=1, and the offending region root. Reproduce in DevTools by selecting .landing-hero-actions__buttons (or an open .modal.show footer) and counting visible elements with btn-forge or btn-primary that are not outline variants.
Remediate (in order):
- Pick one story-aligned primary path; keep a single
btn btn-forge(orbtn-primary) in each hero, modal, and sticky band. - Demote competing primaries to
btn btn-cyan-outline,btn btn-forge-outline, orlanding-hero-secondary-linktext links. - For modals and offcanvas panels, use one filled confirm/submit and an outline cancel—not two filled actions.
- For sticky footer bands, collapse to one filled primary; move secondary paths into a menu or link row.
- Reconcile with
DET.BUTTON.GROUP.MAXso the horizontal row stays ≤ 3 visible actions after demotion. - Re-run
analyze-website-ux.mjsmetrics on hero and modal URLs; align homepage budgets withDET.CONTEXT.BURDEN.