Rule handbook metadata
- Rule
DET.CTA.LABEL_NONEMPTY· lanedeterministic- Page status
- stale
- page_version
c1fec213f1e4fe30dae16656019259037d7301d5b562d51489d35f33105b5de9- generated_at
- 2026-05-19T21:45: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-cta-label_nonempty. Scroll down for Before / After examples and Evidence and remediation steps.
Purpose
Kitchen Sink heroes, cards, and doc chrome expose conversion and navigation through <a class="btn …">, <button>, input[type=button|submit|reset|image], and elements with role="button" or role="link" (components/components.py, components/layouts.py). Screen readers, voice control, and the UX auditor all resolve an accessible name for each visible control. When that name is empty—icon-only markup with no aria-label, whitespace-only link text, or a submit input with no value—users cannot identify the action and automated checks flag a major accessibility defect.
This deterministic rule scans visible interactive controls in the metrics phase (collectCtaLabelNonemptyReport), requires at least MIN_CTA_LABEL_CHARS (1) trimmed character in the computed accessible name, and skips nested controls inside another interactive ancestor, hidden subtrees (aria-hidden, display:none), and inert nodes. Names may come from aria-label, aria-labelledby, visible text, title (when non-empty), input value/alt, descendant img[alt], or .sr-only / .visually-hidden helper text.
Plan: Crawl hero, card footer, and mobile nav URLs. Do: Keep human-readable labels on every .btn; add aria-label for icon-only doc nav toggles. Check: metrics.ctaLabelNonemptyReport.violations is empty. Adjust: Replace placeholder-only CTAs and icon glyphs without names before tuning hierarchy or button-group caps.
Passing signals
landing-hero-actions__buttonsanchors include visible text:btn btn-forge+btn btn-cyan-outlinewith labels such as “Start quickstart” and “Read docs”.- Icon-only controls (mobile offcanvas opener, toolbar glyphs) set
aria-label="Open navigation"(or equivalent) on the<button class="btn btn-forge …">itself. - Card footers use
<a class="btn btn-forge" href="…">Read the guide</a>orbtn btn-forge-outlinewith non-empty link text—not bare icons. aria-labelledbypoints at visible heading or caption text when the control surface is minimal.input type="submit"/type="button"ship a non-emptyvalue;type="image"includes meaningfulalt.metrics.ctaLabelNonemptyReportreportsviolations: []after crawl; evidence does not containempty_accessible_name.
Failing signals
kind: empty-accessible-nameon a visible<a class="btn btn-forge" href="…">with no text,aria-label, or qualifyingaria-labelledbyreference.- Icon-only
<button class="btn btn-forge">containing only a decorative glyph span, with noaria-labeland no.sr-onlyhelper inside the control. - Whitespace-only link text that normalizes to fewer than one character after trim.
href="#"anchors counted as interactive only whenrole="button"orrole="link"is set—still require a non-empty name when visible.- Evidence strings like
empty_accessible_name tag="a"="a.btn-forge"ortag="button"="button.btn-forge"with major severity. - Page passes
DET.CTA.HIERARCHYorDET.BUTTON.GROUP.MAXyet fails here because naming, not count or class role, is the gate.
Before example
Before (failing example)
Governed human + agent delivery
One methodology spine from intent to evidence.
Failing KS markup: hero primary with no visible text and a mobile nav toggle with icon-only content—both resolve to an empty accessible name.
After example
After (passing example)
Passing KS markup: same layout with aria-label on the icon control and visible hero button text.
Evidence and remediation
Evidence: Auditor field metrics.ctaLabelNonemptyReport with minLabelChars: 1, controlCount, and violations[] (kind: empty-accessible-name, tag, selectorHint, optional role / href). Findings cite empty_accessible_name and the control hint (for example a.btn-forge or button.btn-forge[@Ldg]). Reproduce in DevTools by selecting each visible .btn / button and inspecting the accessibility tree name.
Remediate (in order):
- Add visible link or button text to every hero and card CTA (
primary_cta_label,secondary_cta_labelin generators—never emit empty strings). - For icon-only controls, set
aria-labelon the interactive element (matchcomponents/layouts.py:aria-label="Open navigation"). - Prefer
aria-labelledbywhen a visible heading already names the action; ensure referenced nodes are notaria-hidden. - For
input type="submit"/type="button", set a non-emptyvalue; fortype="image", set meaningfulalt. - Re-run
analyze-website-ux.mjsmetrics on affected URLs; reconcile withDET.CTA.HIERARCHYandAI.CREDIBILITY.NO_OVERCLAIMso labels stay specific, not generic filler.