Skip to content

Upgrading the Custom Validator to WCAG 2.2 AAA

Current State

The custom regex-based validator in workers/api/src/services/wcag-validator.ts implements 12 rules targeting WCAG 2.1 AA. Eight of these rules have auto-fix capability.

Rule IDWCAG SCLevelAuto-Fix
document-title2.4.2AYes
html-has-lang3.1.1AYes
image-alt1.1.1AYes
link-name2.4.4AYes
button-name4.1.2AYes
label1.3.1AYes
heading-order1.3.1AANo
landmark-one-mainβ€”Best practiceYes
skip-link2.4.1AYes
meta-viewport1.4.4AANo
color-contrast1.4.3AANo (stub)
document-lang-valid3.1.1ANo

The validator operates entirely on HTML strings using regex pattern matching. It has no DOM, no computed styles, and no layout engine.


Gap Analysis: What WCAG 2.2 AAA Requires

WCAG 2.2 AAA adds criteria on top of 2.0/2.1 AA. The full conformance surface includes every A, AA, and AAA success criterion from WCAG 2.0, 2.1, and 2.2 β€” roughly 86 success criteria total. The custom validator currently covers 10 of those (plus 2 best-practice rules).

The gap breaks down into three tiers based on implementation feasibility.


Tier 1: Regex-Feasible (~6 rules, ~2 days)

Rules that can be checked with regex or simple string analysis against the HTML source.

RuleWCAG SCLevelWhat It Checks
table-has-header1.3.1A<table> elements contain <th> or scope attributes
aria-valid-attr4.1.2Aaria-* attributes are from the spec-defined set
aria-required-attr4.1.2AARIA roles have their required attributes (e.g., role="checkbox" needs aria-checked)
definition-list1.3.1A<dl> only contains <dt> and <dd> children
list-structure1.3.1A<ul>/<ol> only contain <li> children
duplicate-id4.1.1ANo repeated id values in the document

Auto-fix potential: table-has-header (promote first row to <th>), duplicate-id (append suffix). Others are detect-only.

Effort: ~2 developer-days. These are straightforward pattern-match checks with well-defined pass/fail criteria.


Tier 2: Stateful or Computed (~12 rules, ~5 days)

Rules that need more than simple regex β€” color math, multi-pass analysis, or attribute cross-referencing β€” but still don’t require a browser.

RuleWCAG SCLevelWhat It ChecksChallenge
color-contrast (real)1.4.3 / 1.4.6AA / AAAForeground-to-background contrast ratio >= 4.5:1 (AA) or 7:1 (AAA)Parse inline styles + CSS, resolve inherit, compute relative luminance
color-contrast-enhanced1.4.6AAASame as above at 7:1 ratio for normal text, 4.5:1 for large textSame color math, stricter thresholds
autocomplete-valid1.3.5AAautocomplete attribute values are valid tokensValidate against the HTML spec token list
aria-allowed-role4.1.2AElements only use ARIA roles appropriate for their tagMap of tag-to-allowed-roles
aria-hidden-focus4.1.2AElements with aria-hidden="true" are not focusableTrack tabindex, href, <button>, etc. inside hidden subtrees
form-field-multiple-labels1.3.1ANo input has more than one associated <label>Cross-reference for attributes and nesting
identical-links-same-purpose2.4.9AAALinks with same text point to same URLAggregate link text-to-href mapping
link-in-text-block1.4.1ALinks in text blocks are distinguishable by more than color aloneDetect underline/border in inline styles
page-has-heading-one1.3.1APage has at least one <h1>Simple but included here due to needing heading hierarchy context
empty-heading1.3.1AHeadings are not emptyRegex for <h[1-6]> with no text content
empty-table-header1.3.1A<th> elements have contentRegex for <th> with no text
scope-attr-valid1.3.1Ascope attribute is row, col, rowgroup, or colgroupAttribute value validation

Auto-fix potential: empty-heading (remove empty headings), scope-attr-valid (remove invalid scope), page-has-heading-one (promote first heading). Color contrast auto-fix is impractical without a design system.

Effort: ~5 developer-days. Color contrast alone is ~2 days (CSS parsing, color resolution, luminance math). The ARIA rules need a role-to-element mapping table.


Tier 3: Browser-Required (~20+ rules, impractical without a DOM)

These rules require computed styles, layout information, focus management, or runtime behavior that only a real browser can provide. This is exactly what axe-core does.

CategoryExample RulesWCAG SCWhy It Needs a Browser
Focus managementfocus-order-semantics, tabindex2.4.3 (A)Requires sequential focus navigation testing
Target sizetarget-size2.5.8 (AA), 2.5.5 (AAA)Needs computed bounding box of interactive elements
Reflowmeta-viewport-large1.4.10 (AA)Requires rendering at different viewport widths
Text spacingtext-spacing1.4.12 (AA)Needs computed styles after CSS cascade
Animationprefers-reduced-motion2.3.3 (AAA)Requires detecting animated content
Computed contrastCSS variables, currentColor, gradients1.4.3 / 1.4.6Needs resolved computed styles
Visible labelslabel-content-name-mismatch2.5.3 (A)Needs computed accessible name vs. visible text
Orientationcss-orientation-lock1.3.4 (AA)Needs media query evaluation
DraggingDragging alternatives2.5.7 (AA)Needs event handler inspection
Focus appearanceFocus indicator visibility2.4.11 (AA), 2.4.12 (AAA)Needs computed outline/border around focused elements
Consistent navigationNavigation consistency across pages3.2.3 (AA)Requires multi-page comparison
Error identificationForm error messages3.3.1 (A)Requires form submission behavior
Status messagesLive region announcements4.1.3 (AA)Requires aria-live runtime behavior

Auto-fix potential: Minimal. These are structural/behavioral issues.

Effort: Impractical to implement in a regex-based validator. Each rule would require embedding a browser or DOM library (jsdom, linkedom, etc.), at which point you’ve rebuilt axe-core.


Effort Summary

TierRulesEffortAuto-FixableNotes
1. Regex-feasible~6~2 days2-3Simple string pattern checks
2. Stateful/computed~12~5 days3-4Color math, cross-references
3. Browser-required~20+Impractical~0Needs layout engine
Total gap~38+~7 days (Tier 1+2)5-7

Coverage after Tier 1+2: ~24 of ~86 SC (28% of WCAG 2.2 AAA).

Coverage with axe-core (already integrated): 90+ rules, ~70% of WCAG 2.2 AAA with zero additional effort.


Recommendation

Don’t expand the custom validator significantly. The two layers now serve complementary roles:

LayerRoleSpeedCoverage
Custom validatorFast auto-fixing of 8 common issues< 50ms12 rules, 8 fixable
axe-core (now on every file)Comprehensive browser-based audit3-8s90+ rules

The highest-value work on the custom validator is:

  1. Tier 1 rules (~2 days) β€” table-has-header, duplicate-id, aria-valid-attr, list structure checks. These are cheap to add and several are auto-fixable.
  2. Real color-contrast checking (~2 days) β€” Replace the stub that always passes. Parse inline styles and our injected CSS to compute actual contrast ratios. Won’t catch CSS-variable or cascaded colors, but covers the majority of converted HTML output.
  3. Leave Tier 3 to axe-core β€” Anything requiring layout, focus, or computed styles is exactly what the browser-based audit provides.

This yields ~20 custom rules with ~10 auto-fixable at a cost of ~4 days, while axe-core handles the remaining 70+ rules that require a browser.