ure full coverage. Use its detailed violation reports to assign tickets to developers. axe-core excels at detecting ARIA role/state mismatches, region/landmark errors in complex layouts, and color contrast issues within images via OCR-aware checks.
2. Lighthouse for Governance: Run Lighthouse in CI to generate a score. Use this score to gate merges or track progress over time. The score provides a single metric for leadership and correlates accessibility with user experience performance.
3. Rule Synchronization: Ensure your axe-core configuration includes all WCAG 2.2 criteria, as Lighthouse may not enforce these yet. Specifically, target size violations (2.5.8) are caught by axe-core but may be missed by Lighthouse.
Code Implementation
The following TypeScript examples demonstrate how to configure both tools for a production workflow. These examples use distinct interfaces and variable names to illustrate best practices.
axe-core Configuration with WCAG 2.2 Tags
This configuration ensures the scanner evaluates against the full spectrum of WCAG 2.2 AA criteria. It explicitly enables rules that are critical for modern compliance, such as target size and ARIA validation.
import { RunOptions, Rule } from 'axe-core';
export interface AccessibilityAuditConfig {
runOnly: {
type: 'tags';
values: string[];
};
rules: Array<{
id: string;
enabled: boolean;
}>;
}
export const axeComplianceConfig: AccessibilityAuditConfig = {
runOnly: {
type: 'tags',
values: [
'wcag2a',
'wcag2aa',
'wcag21a',
'wcag21aa',
'wcag22a',
'wcag22aa'
]
},
rules: [
{ id: 'target-size', enabled: true },
{ id: 'aria-valid-attr-value', enabled: true },
{ id: 'aria-roles', enabled: true },
{ id: 'landmark-one-main', enabled: true },
{ id: 'color-contrast-enhanced', enabled: true },
{ id: 'image-alt', enabled: true }
]
};
export async function executeAxeScan(
context: Node | Document,
options: Partial<RunOptions> = {}
): Promise<any> {
const { default: axe } = await import('axe-core');
return axe.run(context, {
runOnly: axeComplianceConfig.runOnly,
rules: axeComplianceConfig.rules.reduce((acc, rule) => {
acc[rule.id] = { enabled: rule.enabled };
return acc;
}, {} as Record<string, { enabled: boolean }>),
...options
});
}
Lighthouse CI Configuration
This configuration restricts Lighthouse to the accessibility category to reduce execution time while capturing the score and performance correlation. It uses the mobile preset, which is critical for testing responsive accessibility issues.
import lighthouse from 'lighthouse';
import { Result } from 'lighthouse';
export interface LighthouseAuditOptions {
url: string;
port: number;
outputFormat: 'json' | 'html';
}
export async function runLighthouseAccessibilityCheck(
options: LighthouseAuditOptions
): Promise<Result | null> {
const { url, port, outputFormat } = options;
try {
const runnerResult = await lighthouse(url, {
port: port,
output: outputFormat,
onlyCategories: ['accessibility'],
formFactor: 'mobile',
screenEmulation: {
mobile: true,
width: 360,
height: 640,
deviceScaleFactor: 2.625,
disabled: false
}
});
if (!runnerResult?.lhr) {
throw new Error('Lighthouse audit failed to produce results.');
}
const score = runnerResult.lhr.categories.accessibility?.score;
console.log(`Accessibility Score: ${(score ?? 0) * 100}`);
return runnerResult.lhr;
} catch (error) {
console.error('Lighthouse execution error:', error);
return null;
}
}
Architecture Rationale
- axe-core Rule Selection: The configuration explicitly lists WCAG 2.2 tags. This is necessary because default configurations may not include the latest standards. By targeting
wcag22aa, you ensure checks for target size and enhanced contrast are active.
- Lighthouse Mobile Preset: Accessibility issues often manifest differently on mobile devices due to touch targets and viewport constraints. Using the mobile preset ensures the audit reflects real-world usage patterns.
- Separation of Concerns: axe-core returns detailed violation objects with node selectors, making it ideal for bug tracking systems. Lighthouse returns a score, which is better suited for dashboards. Keeping these separate prevents noise in developer workflows while maintaining visibility for stakeholders.
Pitfall Guide
Teams often encounter specific challenges when integrating automated accessibility tools. The following pitfalls are derived from production experience and highlight common mistakes with actionable fixes.
-
The "Score Satisfaction" Trap
- Explanation: Teams see a Lighthouse score of 95 or 100 and assume the application is accessible. Since Lighthouse only checks 27 rules, a high score can mask dozens of violations that axe-core would catch.
- Fix: Never rely on Lighthouse scores for compliance certification. Use axe-core to validate that all 56 rules pass before declaring an application accessible. Treat the Lighthouse score as a trend metric, not a pass/fail criterion.
-
Ignoring WCAG 2.2 Target Size Requirements
- Explanation: WCAG 2.2 introduced criterion 2.5.8 regarding target size. Lighthouse has partial coverage for WCAG 2.2 and may not flag small touch targets. This leads to mobile usability issues that slip into production.
- Fix: Ensure axe-core is configured with
wcag22aa tags. Verify that the target-size rule is enabled. Test interactive elements on mobile viewports to ensure they meet the minimum dimensions.
-
Underestimating ARIA Complexity
- Explanation: Custom widgets often use ARIA incorrectly, such as mismatched roles and states. Lighthouse may miss subtle ARIA errors, while axe-core performs stricter validation on role/state consistency.
- Fix: Use axe-core to audit custom components. Pay special attention to rules like
aria-valid-attr-value and aria-roles. Implement unit tests that validate ARIA attributes during component rendering.
-
False Positive Fatigue
- Explanation: High false positive rates can lead teams to ignore scanner output. Lighthouse has a higher false positive rate (~5%) compared to axe-core (~3%).
- Fix: Prioritize axe-core for daily development to reduce noise. If using Lighthouse, manually review flagged issues to calibrate expectations. Tune axe-core rules only if there is a legitimate reason, as disabling rules can reduce coverage.
-
Missing Image Contrast Issues
- Explanation: Color contrast checks on text embedded within images are difficult. Lighthouse may skip these or flag them incorrectly. axe-core includes OCR-aware checks that can detect contrast issues in images more accurately.
- Fix: Enable image-related rules in axe-core. For critical images with text, use automated OCR checks provided by axe-core to verify contrast ratios. Consider manual review for complex graphical elements.
-
Lack of Node-Level Reporting
- Explanation: Lighthouse provides a score but lacks the granular detail needed for developers to fix issues quickly. Without node-level reporting, developers waste time hunting for violations.
- Fix: Integrate axe-core into your CI pipeline to generate detailed violation reports. Map axe-core violations directly to Jira or Linear tickets, including the CSS selector and context snippet.
-
Siloed Accessibility Metrics
- Explanation: Accessibility is often treated as a separate concern from performance and user experience. This leads to trade-offs where accessibility is deprioritized for speed.
- Fix: Use Lighthouse to correlate accessibility scores with performance metrics like LCP and CLS. Present a unified dashboard to leadership showing that accessibility improvements do not negatively impact performance.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Compliance Audit | axe-core with WCAG 2.2 AA | Provides full rule coverage and detailed violation reports required for certification. | Low (Tooling cost only) |
| Executive Dashboard | Lighthouse Score | Offers a normalized score out of 100 and correlates with performance metrics for leadership. | Low (CI integration) |
| Developer Workflow | axe-core Node Reports | Granular, actionable data helps developers fix issues quickly without hunting for violations. | Medium (Integration effort) |
| Mobile Optimization | Lighthouse Mobile Preset | Tests accessibility in the context of mobile performance and touch interactions. | Low (Configuration) |
| Legacy Codebase | axe-core with Rule Tuning | Allows selective enabling of rules to manage noise in complex, older applications. | High (Remediation effort) |
Configuration Template
Copy this template to initialize a dual-scanner setup in your project. This includes a package script and a basic configuration file.
{
"scripts": {
"audit:axe": "node scripts/run-axe-audit.js",
"audit:lighthouse": "node scripts/run-lighthouse-audit.js",
"audit:all": "npm run audit:axe && npm run audit:lighthouse"
},
"devDependencies": {
"axe-core": "^4.9.0",
"lighthouse": "^11.0.0"
}
}
// scripts/audit-config.ts
export const auditTargets = [
{ url: 'https://your-app.com/home', type: 'page' },
{ url: 'https://your-app.com/dashboard', type: 'protected' },
{ url: 'https://your-app.com/settings', type: 'interactive' }
];
export const axeTags = [
'wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22a', 'wcag22aa'
];
Quick Start Guide
- Install Dependencies: Run
npm install axe-core lighthouse --save-dev to add the scanners to your project.
- Create Configuration Files: Set up
axeComplianceConfig and LighthouseAuditOptions as shown in the Core Solution section.
- Run Initial Scan: Execute
npm run audit:all to generate baseline reports from both tools.
- Review Results: Analyze axe-core violations for immediate fixes and note the Lighthouse score for tracking.
- Integrate into CI: Add the audit scripts to your pipeline to prevent regressions and monitor trends over time.