skills/wcag-auditor/SKILL.md
# WCAG Auditor Skill This skill provides WCAG 2.1 accessibility audit capabilities, including criteria definitions, check implementations, and scoring logic. ## Purpose Systematically evaluate web pages against WCAG 2.1 Level A and AA success criteria to identify accessibility barriers and provide remediation guidance. ## WCAG 2.1 Overview WCAG is organized around four principles (POUR): - **Perceivable**: Information must be presentable to users - **Operable**: Interface must be usable - *
npx skillsauth add ncklrs/claude-chrome-user-testing skills/wcag-auditorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides WCAG 2.1 accessibility audit capabilities, including criteria definitions, check implementations, and scoring logic.
Systematically evaluate web pages against WCAG 2.1 Level A and AA success criteria to identify accessibility barriers and provide remediation guidance.
WCAG is organized around four principles (POUR):
| Level | Description | Requirements | |-------|-------------|--------------| | A | Minimum | Essential accessibility | | AA | Standard | Most common requirement | | AAA | Enhanced | Highest accessibility |
This skill implements Level A and AA checks.
Most WCAG checks can be performed via JavaScript evaluation:
// Example: Check for images without alt text
(function() {
const images = document.querySelectorAll('img');
const violations = [];
images.forEach(function(img) {
if (!img.hasAttribute('alt')) {
violations.push({
element: img.outerHTML.substring(0, 100),
selector: getSelector(img)
});
}
});
return { passed: violations.length === 0, violations: violations };
})();
The accessibility snapshot provides:
Parse the snapshot to verify semantic structure.
Tab through the page to verify:
Requirement: All non-text content has a text alternative.
Check:
// Find images without alt
const imagesWithoutAlt = document.querySelectorAll('img:not([alt])');
// Find inputs type=image without alt
const imageInputsWithoutAlt = document.querySelectorAll('input[type="image"]:not([alt])');
// Find area elements without alt
const areasWithoutAlt = document.querySelectorAll('area:not([alt])');
Pass if: No violations found Severity: Critical (for functional images), Minor (for decorative)
Requirement: Information, structure, and relationships are programmatically determinable.
Check:
// Check heading hierarchy
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
let lastLevel = 0;
const violations = [];
headings.forEach(function(h) {
const level = parseInt(h.tagName[1]);
if (level > lastLevel + 1) {
violations.push({ element: h, issue: 'Skipped heading level' });
}
lastLevel = level;
});
// Check form labels
const inputs = document.querySelectorAll('input, select, textarea');
inputs.forEach(function(input) {
if (!input.labels || input.labels.length === 0) {
if (!input.getAttribute('aria-label') && !input.getAttribute('aria-labelledby')) {
violations.push({ element: input, issue: 'Missing label' });
}
}
});
Severity: Major
Requirement: All functionality is available via keyboard.
Check: Tab through page and verify all interactive elements are reachable.
Severity: Critical
Requirement: Focus can move away from any component.
Check: After focusing each element, verify Tab/Shift+Tab moves focus.
Severity: Critical
Requirement: Mechanism to bypass repeated content.
Check:
// Look for skip links
const skipLink = document.querySelector('a[href^="#main"], a[href^="#content"], .skip-link');
// Look for landmark regions
const landmarks = document.querySelectorAll('main, [role="main"], nav, [role="navigation"]');
return { hasSkipLink: !!skipLink, hasLandmarks: landmarks.length > 0 };
Severity: Major
Requirement: Web pages have titles describing topic or purpose.
Check:
const title = document.title;
return {
hasTitle: title && title.trim().length > 0,
title: title,
isDescriptive: title && title.length > 5 && !title.match(/^(untitled|home|page)$/i)
};
Severity: Minor
Requirement: Default human language is programmatically determinable.
Check:
const htmlLang = document.documentElement.getAttribute('lang');
const validLangs = ['en', 'es', 'fr', 'de', 'it', 'pt', 'zh', 'ja', 'ko', 'ar', 'ru'];
return {
hasLang: !!htmlLang,
lang: htmlLang,
isValid: validLangs.some(function(l) { return htmlLang && htmlLang.startsWith(l); })
};
Severity: Minor
Requirement: Labels or instructions provided for user input.
Check:
const formFields = document.querySelectorAll('input:not([type="hidden"]), select, textarea');
const violations = [];
formFields.forEach(function(field) {
const hasLabel = field.labels && field.labels.length > 0;
const hasAriaLabel = field.getAttribute('aria-label');
const hasAriaLabelledby = field.getAttribute('aria-labelledby');
const hasTitle = field.getAttribute('title');
if (!hasLabel && !hasAriaLabel && !hasAriaLabelledby && !hasTitle) {
violations.push({ element: field, issue: 'No accessible label' });
}
});
Severity: Critical
Requirement: No duplicate IDs, elements properly nested.
Check:
const allIds = document.querySelectorAll('[id]');
const idMap = {};
const duplicates = [];
allIds.forEach(function(el) {
const id = el.id;
if (idMap[id]) {
duplicates.push(id);
}
idMap[id] = true;
});
return { hasDuplicates: duplicates.length > 0, duplicates: duplicates };
Severity: Minor
Requirement: UI components have accessible name, role, and state.
Check via browser_snapshot: Verify custom widgets have appropriate ARIA.
Severity: Critical
Requirement: Text has contrast ratio of at least 4.5:1 (3:1 for large text).
Check:
function getLuminance(r, g, b) {
var a = [r, g, b].map(function(v) {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
function getContrastRatio(l1, l2) {
var lighter = Math.max(l1, l2);
var darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
// Check text elements
var textElements = document.querySelectorAll('p, span, a, h1, h2, h3, h4, h5, h6, li, td, th, label');
var violations = [];
textElements.forEach(function(el) {
var style = window.getComputedStyle(el);
var color = style.color;
var bgColor = style.backgroundColor;
// ... calculate contrast
});
Severity: Major
Requirement: UI components have 3:1 contrast ratio.
Check: Verify button borders, input borders, focus indicators have sufficient contrast.
Severity: Major
Requirement: Keyboard focus indicator is visible.
Check: Tab through elements and verify focus styles are visible.
Severity: Major
function calculateScore(results) {
var levelA = results.filter(function(r) { return r.level === 'A'; });
var levelAA = results.filter(function(r) { return r.level === 'AA'; });
var aScore = (levelA.filter(function(r) { return r.passed; }).length / levelA.length) * 100;
var aaScore = (levelAA.filter(function(r) { return r.passed; }).length / levelAA.length) * 100;
var overall = (results.filter(function(r) { return r.passed; }).length / results.length) * 100;
return {
levelA: Math.round(aScore),
levelAA: Math.round(aaScore),
overall: Math.round(overall),
grade: getGrade(overall)
};
}
function getGrade(score) {
if (score >= 90) return 'A';
if (score >= 80) return 'B';
if (score >= 70) return 'C';
if (score >= 60) return 'D';
return 'F';
}
| Issue | Fix | |-------|-----| | Missing alt text | Add descriptive alt attribute | | Missing form labels | Associate label with for attribute | | Low contrast | Increase color difference | | No skip link | Add "Skip to main content" link | | Missing lang | Add lang attribute to html element | | Keyboard trap | Ensure Tab/Escape can exit | | No focus visible | Add :focus styles with outline |
Adding alt text:
<!-- Before -->
<img src="chart.png">
<!-- After -->
<img src="chart.png" alt="Sales chart showing 20% growth in Q4">
Adding form label:
<!-- Before -->
<input type="email" placeholder="Email">
<!-- After -->
<label for="email">Email address</label>
<input type="email" id="email">
Adding skip link:
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- ... header/nav ... -->
<main id="main-content">
Improving contrast:
/* Before: 2.5:1 ratio */
.text { color: #999; background: #fff; }
/* After: 4.6:1 ratio */
.text { color: #666; background: #fff; }
When violations are found, annotate them:
See commands/wcag-audit.md for full report format templates.
development
Comprehensive persona-based user testing skill for web applications. Simulates how real users from different demographics interact with interfaces, including realistic timing, behavioral patterns, and frustration triggers. Use when: - Testing user interfaces before release - Validating UX flows from diverse perspectives - Conducting accessibility reviews - Optimizing onboarding or checkout experiences - Getting feedback on form design
development
# Stripe Checkout Testing Skill This skill provides guidance for testing Stripe checkout flows with any persona. It handles test card data, form detection, and payment-specific narration. ## Purpose Enable realistic user testing of Stripe payment flows using official test cards, with persona-appropriate reactions to checkout experiences. ## Test Card Reference Load card data from `test-cards.json`. Key scenarios: | Scenario | Card | When to Use | |----------|------|-------------| | `succes
testing
# Smoke Testing Skill Run pre-configured smoke tests for common user flows. Quick validation that critical functionality works. ## What is Smoke Testing? Smoke testing is a quick sanity check to ensure basic functionality works before deeper testing. The name comes from electronics - if you turn on a circuit and smoke comes out, you know something is wrong without further testing. ## When to Use - Before releases to catch obvious breaks - After deployments to verify functionality - In CI/CD
development
# Session Recorder Skill Record user testing sessions as Playwright Traces for replay and analysis. Traces capture the full testing session including screenshots, DOM snapshots, network requests, and console logs. ## When to Use - `--record` flag is present in the command - User wants to replay or share a testing session - Need to debug issues found during testing - Creating documentation of UX issues ## Trace File Format Playwright Trace files (`.zip`) contain: - **Screenshots**: Captured