AgentStack
SKILL verified Apache-2.0 Self-run

Accessibility Wcag

skill-medy-gribkov-arcana-accessibility-wcag · by medy-gribkov

Audit and fix web accessibility issues. Enforce WCAG 2.1 AA compliance, semantic HTML, ARIA patterns, keyboard navigation, screen reader compatibility.

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-medy-gribkov-arcana-accessibility-wcag

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Accessibility Wcag? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Accessibility & WCAG Compliance

Audit web applications for WCAG 2.1/2.2 compliance, fix accessibility barriers, implement proper semantic HTML and ARIA patterns.

BAD: Common Accessibility Violations

// BAD: Div button without keyboard support

  Submit

// BAD: Color-only error indication

Error

// BAD: Image without alt text

// BAD: Aria-label overuse
Click me

// BAD: Auto-focus abuse

   {/* Steals focus */}

// BAD: No heading hierarchy
Page Title
Section {/* Skips h2, h3 */}

// BAD: Form without labels

  Choose...

// BAD: Custom dropdown without keyboard support

  {options.map(opt =>  select(opt)}>{opt})}

GOOD: Accessible Implementations

// GOOD: Semantic button with proper keyboard support

  Submit

// GOOD: Multi-sensory error indication

  Error:
  Invalid email format

// GOOD: Descriptive alt text (or empty for decorative)

// GOOD: Redundant aria-label removed
Click me

// GOOD: Managed focus with trap
function Modal({ open, onClose, children }) {
  const modalRef = useRef(null);

  useEffect(() => {
    if (!open) return;

    const previousFocus = document.activeElement as HTMLElement;
    modalRef.current?.focus();

    return () => previousFocus?.focus();
  }, [open]);

  return (
     {
        if (e.key === 'Escape') onClose();
      }}
    >
      {children}
    
  );
}

// GOOD: Proper heading hierarchy
Page Title
Main Section
Subsection
Another Section

// GOOD: Labeled form controls
Email address

  We'll never share your email

Country

  Select a country
  United States

// GOOD: Accessible custom dropdown
function Dropdown({ options, value, onChange, label }) {
  const [open, setOpen] = useState(false);
  const [activeIndex, setActiveIndex] = useState(0);
  const buttonRef = useRef(null);
  const listRef = useRef(null);

  const handleKeyDown = (e: KeyboardEvent) => {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setActiveIndex(prev => Math.min(prev + 1, options.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setActiveIndex(prev => Math.max(prev - 1, 0));
        break;
      case 'Enter':
      case ' ':
        e.preventDefault();
        onChange(options[activeIndex]);
        setOpen(false);
        buttonRef.current?.focus();
        break;
      case 'Escape':
        setOpen(false);
        buttonRef.current?.focus();
        break;
    }
  };

  return (
    
       setOpen(!open)}
      >
        {label}: {value}
      

      {open && (
        
          {options.map((opt, i) => (
             {
                onChange(opt);
                setOpen(false);
                buttonRef.current?.focus();
              }}
            >
              {opt}
            
          ))}
        
      )}
    
  );
}

Keyboard Navigation Patterns

// Roving tabindex for toolbar
function Toolbar({ items }) {
  const [focusedIndex, setFocusedIndex] = useState(0);

  const handleKeyDown = (e: KeyboardEvent, index: number) => {
    let newIndex = index;

    if (e.key === 'ArrowRight') {
      newIndex = (index + 1) % items.length;
    } else if (e.key === 'ArrowLeft') {
      newIndex = (index - 1 + items.length) % items.length;
    } else if (e.key === 'Home') {
      newIndex = 0;
    } else if (e.key === 'End') {
      newIndex = items.length - 1;
    } else {
      return;
    }

    e.preventDefault();
    setFocusedIndex(newIndex);
  };

  return (
    
      {items.map((item, i) => (
         handleKeyDown(e, i)}
          onFocus={() => setFocusedIndex(i)}
        >
          {item.label}
        
      ))}
    
  );
}

// Skip navigation link
function Layout({ children }) {
  return (
    <>
      
        Skip to main content
      

      
        {/* Nav items */}
      

      
        {children}
      
    
  );
}

// Focus-visible styling (CSS)
/* Show focus only for keyboard users */
button:focus {
  outline: none;
}

button:focus-visible {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

Live Regions and Dynamic Content

// Status announcements
function Toast({ message, type }) {
  return (
    
      {message}
    
  );
}

// Urgent alerts
function ErrorAlert({ message }) {
  return (
    
      Error: {message}
    
  );
}

// Loading state announcement
function DataTable({ loading, data }) {
  return (
    
      {loading && (
        
          Loading data...
          
        
      )}

      
        Sales data for 2025
        
          
            Month
            Revenue
          
        
        
          {data.map(row => (
            
              {row.month}
              {row.revenue}
            
          ))}
        
      
    
  );
}

Color Contrast and Motion

/* GOOD: WCAG AA contrast ratios */
.text-normal {
  color: #1a1a1a; /* 4.5:1 on white */
  background: #ffffff;
}

.text-large {
  font-size: 18px;
  font-weight: 700;
  color: #4a4a4a; /* 3:1 on white (large text) */
}

.button-primary {
  background: #0066cc; /* 4.5:1 with white text */
  color: #ffffff;
}

/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* GOOD: Graceful animation degradation */
@media (prefers-reduced-motion: no-preference) {
  .fade-in {
    animation: fadeIn 0.3s ease-in;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fade-in {
    opacity: 1; /* Skip animation, show immediately */
  }
}

Automated Testing

// axe-core integration (Jest + React Testing Library)
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';

expect.extend(toHaveNoViolations);

test('LoginForm has no accessibility violations', async () => {
  const { container } = render();
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

// Playwright accessibility audit
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('Homepage should not have accessibility violations', async ({ page }) => {
  await page.goto('http://localhost:3000');

  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
    .analyze();

  expect(results.violations).toEqual([]);
});

// Cypress accessibility testing
describe('Accessibility', () => {
  beforeEach(() => {
    cy.visit('/');
    cy.injectAxe();
  });

  it('Has no detectable accessibility violations', () => {
    cy.checkA11y();
  });

  it('Has no violations on specific component', () => {
    cy.checkA11y('.navigation-menu');
  });

  it('Respects wcag2aa standard', () => {
    cy.checkA11y(null, {
      runOnly: {
        type: 'tag',
        values: ['wcag2aa']
      }
    });
  });
});

Workflow

When auditing accessibility:

  1. Run automated tools first: axe DevTools, Lighthouse, WAVE
  2. Check keyboard navigation: Tab through entire page, verify focus order
  3. Test with screen reader: NVDA (Windows), VoiceOver (Mac), JAWS
  4. Verify color contrast: Use contrast checker for all text/UI elements
  5. Validate ARIA: Ensure roles/states match component behavior
  6. Test dynamic content: Live regions announce properly
  7. Check form accessibility: Labels, errors, validation messages
  8. Verify responsive behavior: Touch targets >= 44x44px on mobile

Common WCAG 2.1 Level AA failures:

  • Missing alt text (1.1.1)
  • Insufficient contrast (1.4.3)
  • No keyboard access (2.1.1)
  • Missing form labels (3.3.2)
  • Invalid HTML/ARIA (4.1.1, 4.1.2)

Fix priority: Critical (blocks screen readers) > High (keyboard issues) > Medium (contrast) > Low (enhancements).

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.