npm package: @rivtor/a11y-guard
Description: WCAG 2.1 AA accessibility enforcement with ESLint plugin, TypeScript transformer, and runtime monitor
Author: Rivtor
Latest version: 1.1.0

# @rivtor/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

```bash
npm install @rivtor/a11y-guard
# or
yarn add @rivtor/a11y-guard
# or
pnpm add @rivtor/a11y-guard
```

## Quick Start

### 1. Configure ESLint Plugin

Add to your `.eslintrc.js`:

```javascript
module.exports = {
  plugins: ['@rivtor/a11y-guard'],
  rules: {
    '@rivtor/a11y-guard/button-has-accessible-name': 'error',
    '@rivtor/a11y-guard/img-has-alt': 'error',
    '@rivtor/a11y-guard/input-has-label': 'error',
    '@rivtor/a11y-guard/anchor-has-content': 'error',
    '@rivtor/a11y-guard/heading-valid-level': 'warn',
    '@rivtor/a11y-guard/valid-aria-role': 'error',
    '@rivtor/a11y-guard/no-positive-tabindex': 'error',
    '@rivtor/a11y-guard/onclick-with-keyboard': 'error',
    '@rivtor/a11y-guard/alt-text-not-decorative': 'warn',
    '@rivtor/a11y-guard/th-has-scope': 'error',
    // ... and 27 more rules
  },
};
```

### 2. Server-Side Color Validation

```typescript
import { checkColorContrast, auditColorScheme } from '@rivtor/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

```typescript
'use client';
import { monitorAccessibility, announceScreenReader } from '@rivtor/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

```tsx
import { VocoButton, VocoInput, VocoForm } from '@rivtor/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

```bash
# Check color contrast
npx @rivtor/a11y-guard check --foreground #fff --background #000
# Output: ✓ Contrast ratio 21:1 passes WCAG AA and AAA

# Audit color palette from JSON file
npx @rivtor/a11y-guard audit --file ./colors.json

# Analyze with JSON output
npx @rivtor/a11y-guard check -f #3b82f6 -b #ffffff --json
# Output: {"ratio":4.51,"aa":true,"aaa":false,"passesLargeText":true}
```

## API Reference

### Server Functions

```typescript
import { a11y } from '@rivtor/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, level
```

### Client Functions

```typescript
import { a11y } from '@rivtor/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

```tsx
<VocoButton
  variant="primary"
  size="medium"
  onClick={handleClick}
>
  Click Me
</VocoButton>
```

### VocoInput

```tsx
<VocoInput
  label="Email"
  type="email"
  name="email"
  required
  autoComplete="email"
  placeholder="user@example.com"
/>
```

### VocoForm

```tsx
<VocoForm onSubmit={handleSubmit} aria-label="Contact form">
  {/* Form fields */}
</VocoForm>
```

## License

MIT

---

Made with ❤️ by [Rivtor](https://rivtor.com)