agentsclimarketplace

Wcag auditor

Skill ncklrs/claude-chrome-user-testing/skills/wcag-auditor

Claude Code plugin for persona-based user testing. Simulates Boomers, Millennials, Gen Z, and Gen Alpha users to find UX issues before real users do.From the repository description

Install
npx -y skills add ncklrs/claude-chrome-user-testing --skill wcag-auditor

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 19 stars19 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.7 KB, as published. Nobody here has run it

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
  • Understandable: Information and operation must be understandable
  • Robust: Content must be interpretable by assistive technologies

Conformance Levels

LevelDescriptionRequirements
AMinimumEssential accessibility
AAStandardMost common requirement
AAAEnhancedHighest accessibility

This skill implements Level A and AA checks.

Check Implementation Guide

Using browser_evaluate for DOM 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 };
})();

Using browser_snapshot for Accessibility Tree

The accessibility snapshot provides:

  • ARIA roles and properties
  • Accessible names
  • Focusable elements
  • Heading structure

Parse the snapshot to verify semantic structure.

Using browser_press_key for Keyboard Tests

Tab through the page to verify:

  • All interactive elements are reachable
  • Focus order is logical
  • No focus traps exist

Criteria Reference

Level A Criteria

1.1.1 Non-text Content

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)


1.3.1 Info and Relationships

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


2.1.1 Keyboard

Requirement: All functionality is available via keyboard.

Check: Tab through page and verify all interactive elements are reachable.

Severity: Critical


2.1.2 No Keyboard Trap

Requirement: Focus can move away from any component.

Check: After focusing each element, verify Tab/Shift+Tab moves focus.

Severity: Critical


2.4.1 Bypass Blocks

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


2.4.2 Page Titled

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


3.1.1 Language of Page

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


3.3.2 Labels or Instructions

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


4.1.1 Parsing

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


4.1.2 Name, Role, Value

Requirement: UI components have accessible name, role, and state.

Check via browser_snapshot: Verify custom widgets have appropriate ARIA.

Severity: Critical


Level AA Criteria

1.4.3 Contrast (Minimum)

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


1.4.11 Non-text Contrast

Requirement: UI components have 3:1 contrast ratio.

Check: Verify button borders, input borders, focus indicators have sufficient contrast.

Severity: Major


2.4.7 Focus Visible

Requirement: Keyboard focus indicator is visible.

Check: Tab through elements and verify focus styles are visible.

Severity: Major


Scoring Logic

Calculate Scores

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';
}

Remediation Guidance

Common Fixes

IssueFix
Missing alt textAdd descriptive alt attribute
Missing form labelsAssociate label with for attribute
Low contrastIncrease color difference
No skip linkAdd "Skip to main content" link
Missing langAdd lang attribute to html element
Keyboard trapEnsure Tab/Escape can exit
No focus visibleAdd :focus styles with outline

Code Examples

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; }

Integration with Screenshot Annotator

When violations are found, annotate them:

  1. For each violation with identifiable element
  2. Get element position
  3. Add red box annotation with criterion ID as label
  4. Take screenshot
  5. Include in report

Report Templates

See commands/wcag-audit.md for full report format templates.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.