@vocoweb/a11y-guard
v1.1.0
Published
WCAG 2.1 AA accessibility enforcement with ESLint plugin, TypeScript transformer, and runtime monitor
Maintainers
Readme
@vocoweb/a11y-guard
Production-ready WCAG 2.1 AA accessibility enforcement with ESLint plugin and runtime monitor
Features
- 37 ESLint Rules: Compile-time accessibility checking for common violations
- Color Contrast Validation: WCAG AA/AAA compliance checking with ratio analysis
- ARIA Attribute Validation: Detailed error messages for ARIA misconfigurations
- Runtime DOM Monitoring: Real-time accessibility violation detection
- Pre-Built Components: WCAG-compliant React components (VocoButton, VocoInput, VocoForm)
- CLI Tool: Standalone accessibility checking for design systems
Installation
npm install @vocoweb/a11y-guard
# or
yarn add @vocoweb/a11y-guard
# or
pnpm add @vocoweb/a11y-guardQuick Start
1. Configure ESLint Plugin
Add to your .eslintrc.js:
module.exports = {
plugins: ['@vocoweb/a11y-guard'],
rules: {
'@vocoweb/a11y-guard/button-has-accessible-name': 'error',
'@vocoweb/a11y-guard/img-has-alt': 'error',
'@vocoweb/a11y-guard/input-has-label': 'error',
'@vocoweb/a11y-guard/anchor-has-content': 'error',
'@vocoweb/a11y-guard/heading-valid-level': 'warn',
'@vocoweb/a11y-guard/valid-aria-role': 'error',
'@vocoweb/a11y-guard/no-positive-tabindex': 'error',
'@vocoweb/a11y-guard/onclick-with-keyboard': 'error',
'@vocoweb/a11y-guard/alt-text-not-decorative': 'warn',
'@vocoweb/a11y-guard/th-has-scope': 'error',
// ... and 27 more rules
},
};2. Server-Side Color Validation
import { checkColorContrast, auditColorScheme } from '@vocoweb/a11y-guard/server';
// Check color contrast ratio
const result = checkColorContrast('#ffffff', '#000000');
console.log(result);
// {
// ratio: 21,
// aa: true,
// aaa: true,
// passesNormalText: true,
// passesLargeText: true
// }
// Audit entire color palette
const violations = auditColorScheme({
primary: { foreground: '#ffffff', background: '#3b82f6' },
secondary: { foreground: '#1f2937', background: '#f3f4f6' },
}, 'AA');
if (violations.length > 0) {
console.log('Accessibility violations found:', violations);
}3. Runtime Monitoring
'use client';
import { monitorAccessibility, announceScreenReader } from '@vocoweb/a11y-guard/client';
export function AccessibilityMonitor() {
useEffect(() => {
// Monitor for accessibility violations
const cleanup = monitorAccessibility((violations) => {
console.warn('Found accessibility violations:', violations);
});
return cleanup;
}, []);
const handleSuccess = () => {
// Announce to screen readers
announceScreenReader('Form submitted successfully', 'polite');
};
return <button onClick={handleSuccess}>Submit</button>;
}4. Pre-Built Components
import { VocoButton, VocoInput, VocoForm } from '@vocoweb/a11y-guard/react';
export default function ContactForm() {
return (
<VocoForm onSubmit={handleSubmit}>
<VocoInput
label="Email Address"
type="email"
name="email"
required
autoComplete="email"
/>
<VocoInput
label="Message"
type="textarea"
name="message"
required
/>
<VocoButton type="submit">Send Message</VocoButton>
</VocoForm>
);
}5. CLI Tool
# Check color contrast
npx @vocoweb/a11y-guard check --foreground #fff --background #000
# Output: ✓ Contrast ratio 21:1 passes WCAG AA and AAA
# Audit color palette from JSON file
npx @vocoweb/a11y-guard audit --file ./colors.json
# Analyze with JSON output
npx @vocoweb/a11y-guard check -f #3b82f6 -b #ffffff --json
# Output: {"ratio":4.51,"aa":true,"aaa":false,"passesLargeText":true}API Reference
Server Functions
import { a11y } from '@vocoweb/a11y-guard/server';
// Check color contrast
const result = a11y.checkColorContrast(foreground, background);
// Validate ARIA attributes
const ariaResult = a11y.validateAriaLabel(undefined, undefined, 'button');
// { valid: false, issues: [...] }
// Audit color scheme
const violations = a11y.auditColorScheme(colors, 'AA');
// Validate interactive elements
const elementViolations = a11y.validateInteractiveElements(elements);
// Get suggested color pairings
const suggestions = a11y.getSuggestedColorPairings('#3b82f6', 4.5);
// Find accessible color
const accessible = a11y.findAccessibleColor('#ff0000', '#ffffff', 4.5);
// Check if text is "large text" per WCAG
const isLarge = a11y.isLargeText(24, false); // fontSize, bold
// Get required contrast ratio
const ratio = a11y.getRequiredContrast(16, false, 'AA'); // fontSize, bold, levelClient Functions
import { a11y } from '@vocoweb/a11y-guard/client';
// Test keyboard navigation
const navResult = a11y.testKeyboardNavigation(element);
// Monitor DOM for violations
const cleanup = a11y.monitorAccessibility((violations) => {
console.log('Violations:', violations);
});
// Announce to screen readers
a11y.announceScreenReader('Loading complete', 'polite');
// Check if element is focusable
const focusable = a11y.isFocusable(element);
// Get tab order
const order = a11y.getTabOrder(container);
// Detect skip links
const skipLinks = a11y.detectSkipLinks();All ESLint Rules
| Rule | Description | Level |
|------|-------------|-------|
| button-has-accessible-name | Buttons must have accessible name | error |
| img-has-alt | Images must have alt attribute | error |
| input-has-label | Inputs must have associated label | error |
| anchor-has-content | Anchors must have discernible text | error |
| heading-valid-level | Heading levels should not be skipped | warning |
| valid-aria-role | ARIA role must be valid | error |
| no-positive-tabindex | Avoid positive tabindex values | error |
| onclick-with-keyboard | onClick needs keyboard handler | error |
| alt-text-not-decorative | Use empty alt for decorative images | warning |
| th-has-scope | Table headers must have scope | error |
| no-autofocus | Avoid autofocus attribute | suggestion |
| no-aria-hidden-on-focusable | Avoid aria-hidden on focusable elements | error |
| label-has-for | Label must have htmlFor attribute | error |
| no-distracting-elements | Avoid distracting elements | error |
| media-has-caption | Media elements must have caption | error |
| no-access-key-attrs | Avoid accesskey attribute | suggestion |
| anchor-is-valid | Anchor href must be valid | error |
| aria-props | ARIA attributes must be valid | error |
| aria-unsupported-elements | Do not use ARIA on unsupported elements | error |
| role-has-required-aria-props | Role must have required ARIA props | error |
| role-supports-aria-props | Role must support ARIA props | error |
| no-redundant-roles | Avoid redundant roles | suggestion |
| no-interactive-element-to-noninteractive-role | Interactive elements should not have non-interactive roles | error |
| no-noninteractive-element-to-interactive-role | Non-interactive elements should not have interactive roles | error |
| no-noninteractive-tabindex | Non-interactive elements should not have tabindex | error |
| interactive-supports-focus | Interactive elements must be focusable | error |
| no-static-element-interactions | Static elements should not have interactive handlers | error |
| click-events-have-key-events | Click events must have keyboard events | error |
| mouse-events-have-key-events | Mouse events must have keyboard events | error |
| no-onchange | Prefer onBlur over onChange | suggestion |
| no-focus-without-onchange | onFocus without onChange is not accessible | error |
| label-text-no-mixed-placeholder | Label text should not be used as placeholder | suggestion |
| no-placeholder-label | Do not use placeholder as label | error |
| no-title-in-attribute | Avoid title attribute for accessibility | suggestion |
React Components
VocoButton
<VocoButton
variant="primary"
size="medium"
onClick={handleClick}
>
Click Me
</VocoButton>VocoInput
<VocoInput
label="Email"
type="email"
name="email"
required
autoComplete="email"
placeholder="[email protected]"
/>VocoForm
<VocoForm onSubmit={handleSubmit} aria-label="Contact form">
{/* Form fields */}
</VocoForm>License
MIT
Made with ❤️ by VocoWeb
