Generated banner for Accessibility Compliance
Generated logo for Accessibility Compliance

Accessibility Compliance

Seth HobsonCommunityCreated by Seth Hobson

  • Skill
  • Guidelines
  • Software development
  • MIT
  • No issues found

From the author

Implement WCAG 2.2 compliant interfaces with mobile accessibility, inclusive design patterns, and assistive technology support. Use when auditing accessibility, implementing ARIA patterns, building for screen readers, or ensuring inclusive user experiences.
— the description by Seth Hobson

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured approach to building inclusive digital experiences. It covers WCAG 2.2 compliance, ARIA patterns, keyboard navigation, and mobile accessibility. It emphasizes semantic HTML, proper focus management, and assistive technology support to ensure applications are perceivable, operable, understandable, and robust for all users.

What it does

  • Provides patterns for WCAG 2.2 compliant UI components like buttons, modals, and tabs.
  • Explains the correct usage of ARIA roles, states, and properties.
  • Outlines requirements for keyboard navigation and focus management.
  • Details mobile accessibility standards including touch target sizing and gesture support.
  • Offers a checklist for WCAG 2.2 success criteria.
  • Suggests manual and automated testing tools for accessibility audits.

When to use it

  • When designing or developing new web components that require accessibility compliance.
  • When auditing an existing application for WCAG 2.2 violations.
  • When implementing complex interactive widgets like accordions or dialogs.
  • When optimizing interfaces for screen readers and keyboard-only users.

Advantages

  • Promotes inclusive design by prioritizing semantic HTML.
  • Reduces common accessibility pitfalls like keyboard traps and missing labels.
  • Provides ready-to-use code patterns for accessible UI elements.
  • Ensures compliance with modern accessibility standards.

Usage examples

“How do I create an accessible modal dialog?”
The skill provides a pattern using focus trapping, Escape key handling, and proper ARIA labeling to ensure the modal is fully accessible to screen readers and keyboard users.
“What are the requirements for mobile touch targets?”
The skill specifies a minimum touch target size of 44x44dp to ensure ease of interaction for all users on mobile devices.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/wshobson/accessibility-compliance/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip accessibility-compliance.zip -d .agents/skills

Documentation by Seth Hobson

Accessibility Compliance

Master accessibility implementation to create inclusive experiences that work for everyone, including users with disabilities.

When to Use This Skill

  • Implementing WCAG 2.2 Level AA or AAA compliance

  • Building screen reader accessible interfaces

  • Adding keyboard navigation to interactive components

  • Implementing focus management and focus trapping

  • Creating accessible forms with proper labeling

  • Supporting reduced motion and high contrast preferences

  • Building mobile accessibility features (iOS VoiceOver, Android TalkBack)

  • Conducting accessibility audits and fixing violations

Detailed patterns and worked examples

Detailed pattern documentation lives in references/details.md. Read that file when the navigation tier above is insufficient.

Best Practices

  1. Use Semantic HTML: Prefer native elements over ARIA when possible

  2. Test with Real Users: Include people with disabilities in user testing

  3. Keyboard First: Design interactions to work without a mouse

  4. Don't Disable Focus Styles: Style them, don't remove them

  5. Provide Text Alternatives: All non-text content needs descriptions

  6. Support Zoom: Content should work at 200% zoom

  7. Announce Changes: Use live regions for dynamic content

  8. Respect Preferences: Honor prefers-reduced-motion and prefers-contrast

Common Issues

  • Missing alt text: Images without descriptions

  • Poor color contrast: Text hard to read against background

  • Keyboard traps: Focus stuck in component

  • Missing labels: Form inputs without associated labels

  • Auto-playing media: Content that plays without user initiation

  • Inaccessible custom controls: Recreating native functionality poorly

  • Missing skip links: No way to bypass repetitive content

  • Focus order issues: Tab order doesn't match visual order

Testing Tools

  • Automated: axe DevTools, WAVE, Lighthouse

  • Manual: VoiceOver (macOS/iOS), NVDA/JAWS (Windows), TalkBack (Android)

  • Simulators: NoCoffee (vision), Silktide (various disabilities)

Files

references/details.md

accessibility-compliance — detailed patterns and worked examples

Core Capabilities

1. WCAG 2.2 Guidelines
  • Perceivable: Content must be presentable in different ways

  • Operable: Interface must be navigable with keyboard and assistive tech

  • Understandable: Content and operation must be clear

  • Robust: Content must work with current and future assistive technologies

2. ARIA Patterns
  • Roles: Define element purpose (button, dialog, navigation)

  • States: Indicate current condition (expanded, selected, disabled)

  • Properties: Describe relationships and additional info (labelledby, describedby)

  • Live regions: Announce dynamic content changes

3. Keyboard Navigation
  • Focus order and tab sequence

  • Focus indicators and visible focus states

  • Keyboard shortcuts and hotkeys

  • Focus trapping for modals and dialogs

4. Screen Reader Support
  • Semantic HTML structure

  • Alternative text for images

  • Proper heading hierarchy

  • Skip links and landmarks

5. Mobile Accessibility
  • Touch target sizing (44x44dp minimum)

  • VoiceOver and TalkBack compatibility

  • Gesture alternatives

  • Dynamic Type support

Quick Reference

WCAG 2.2 Success Criteria Checklist
LevelCriterionDescription
A1.1.1Non-text content has text alternatives
A1.3.1Info and relationships programmatically determinable
A2.1.1All functionality keyboard accessible
A2.4.1Skip to main content mechanism
AA1.4.3Contrast ratio 4.5:1 (text), 3:1 (large text)
AA1.4.11Non-text contrast 3:1
AA2.4.7Focus visible
AA2.5.8Target size minimum 24x24px (NEW in 2.2)
AAA1.4.6Enhanced contrast 7:1
AAA2.5.5Target size minimum 44x44px

Key Patterns

Pattern 1: Accessible Button
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: "primary" | "secondary";
  isLoading?: boolean;
}

function AccessibleButton({
  children,
  variant = "primary",
  isLoading = false,
  disabled,
  ...props
}: ButtonProps) {
  return (
    <button
      // Disable when loading
      disabled={disabled || isLoading}
      // Announce loading state to screen readers
      aria-busy={isLoading}
      // Describe the button's current state
      aria-disabled={disabled || isLoading}
      className={cn(
        // Visible focus ring
        "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
        // Minimum touch target size (44x44px)
        "min-h-[44px] min-w-[44px]",
        variant === "primary" && "bg-primary text-primary-foreground",
        (disabled || isLoading) && "opacity-50 cursor-not-allowed",
      )}
      {...props}
    >
      {isLoading ? (
        <>
          <span className="sr-only">Loading</span>
          <Spinner aria-hidden="true" />
        </>
      ) : (
        children
      )}
    </button>
  );
}
Pattern 2: Accessible Modal Dialog
import * as React from "react";
import { FocusTrap } from "@headlessui/react";

interface DialogProps {
  isOpen: boolean;
  onClose: () => void;
  title: string;
  children: React.ReactNode;
}

function AccessibleDialog({ isOpen, onClose, title, children }: DialogProps) {
  const titleId = React.useId();
  const descriptionId = React.useId();

  // Close on Escape key
  React.useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "Escape" && isOpen) {
        onClose();
      }
    };
    document.addEventListener("keydown", handleKeyDown);
    return () => document.removeEventListener("keydown", handleKeyDown);
  }, [isOpen, onClose]);

  // Prevent body scroll when open
  React.useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = "hidden";
    }
    return () => {
      document.body.style.overflow = "";
    };
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby={titleId}
      aria-describedby={descriptionId}
    >
      {/* Backdrop */}
      <div
        className="fixed inset-0 bg-black/50"
        aria-hidden="true"
        onClick={onClose}
      />

      {/* Focus trap container */}
      <FocusTrap>
        <div className="fixed inset-0 flex items-center justify-center p-4">
          <div className="bg-background rounded-lg shadow-lg max-w-md w-full p-6">
            <h2 id={titleId} className="text-lg font-semibold">
              {title}
            </h2>
            <div id={descriptionId}>{children}</div>
            <button
              onClick={onClose}
              className="absolute top-4 right-4"
              aria-label="Close dialog"
            >
              <X className="h-4 w-4" />
            </button>
          </div>
        </div>
      </FocusTrap>
    </div>
  );
}
Pattern 3: Accessible Form
function AccessibleForm() {
  const [errors, setErrors] = React.useState<Record<string, string>>({});

  return (
    <form aria-describedby="form-errors" noValidate>
      {/* Error summary for screen readers */}
      {Object.keys(errors).length > 0 && (
        <div
          id="form-errors"
          role="alert"
          aria-live="assertive"
          className="bg-destructive/10 border border-destructive p-4 rounded-md mb-4"
        >
          <h2 className="font-semibold text-destructive">
            Please fix the following errors:
          </h2>
          <ul className="list-disc list-inside mt-2">
            {Object.entries(errors).map(([field, message]) => (
              <li key={field}>
                <a href={`#${field}`} className="underline">
                  {message}
                </a>
              </li>
            ))}
          </ul>
        </div>
      )}

      {/* Required field with error */}
      <div className="space-y-2">
        <label htmlFor="email" className="block font-medium">
          Email address
          <span aria-hidden="true" className="text-destructive ml-1">
            *
          </span>
          <span className="sr-only">(required)</span>
        </label>
        <input
          id="email"
          name="email"
          type="email"
          required
          aria-required="true"
          aria-invalid={!!errors.email}
          aria-describedby={errors.email ? "email-error" : "email-hint"}
          className={cn(
            "w-full px-3 py-2 border rounded-md",
            errors.email && "border-destructive",
          )}
        />
        {errors.email ? (
          <p id="email-error" className="text-sm text-destructive" role="alert">
            {errors.email}
          </p>
        ) : (
          <p id="email-hint" className="text-sm text-muted-foreground">
            We'll never share your email.
          </p>
        )}
      </div>

      <button type="submit" className="mt-4">
        Submit
      </button>
    </form>
  );
}
function SkipLink() {
  return (
    <a
      href="#main-content"
      className={cn(
        // Hidden by default, visible on focus
        "sr-only focus:not-sr-only",
        "focus:absolute focus:top-4 focus:left-4 focus:z-50",
        "focus:bg-background focus:px-4 focus:py-2 focus:rounded-md",
        "focus:ring-2 focus:ring-primary",
      )}
    >
      Skip to main content
    </a>
  );
}

// In layout
function Layout({ children }) {
  return (
    <>
      <SkipLink />
      <header>...</header>
      <nav aria-label="Main navigation">...</nav>
      <main id="main-content" tabIndex={-1}>
        {children}
      </main>
      <footer>...</footer>
    </>
  );
}
Pattern 5: Live Region for Announcements
function useAnnounce() {
  const [message, setMessage] = React.useState("");

  const announce = React.useCallback(
    (text: string, priority: "polite" | "assertive" = "polite") => {
      setMessage(""); // Clear first to ensure re-announcement
      setTimeout(() => setMessage(text), 100);
    },
    [],
  );

  const Announcer = () => (
    <div
      role="status"
      aria-live="polite"
      aria-atomic="true"
      className="sr-only"
    >
      {message}
    </div>
  );

  return { announce, Announcer };
}

// Usage
function SearchResults({ results, isLoading }) {
  const { announce, Announcer } = useAnnounce();

  React.useEffect(() => {
    if (!isLoading && results) {
      announce(`${results.length} results found`);
    }
  }, [results, isLoading, announce]);

  return (
    <>
      <Announcer />
      <ul>{/* results */}</ul>
    </>
  );
}

Color Contrast Requirements

// Contrast ratio utilities
function getContrastRatio(foreground: string, background: string): number {
  const fgLuminance = getLuminance(foreground);
  const bgLuminance = getLuminance(background);
  const lighter = Math.max(fgLuminance, bgLuminance);
  const darker = Math.min(fgLuminance, bgLuminance);
  return (lighter + 0.05) / (darker + 0.05);
}

// WCAG requirements
const CONTRAST_REQUIREMENTS = {
  // Normal text (<18pt or <14pt bold)
  normalText: {
    AA: 4.5,
    AAA: 7,
  },
  // Large text (>=18pt or >=14pt bold)
  largeText: {
    AA: 3,
    AAA: 4.5,
  },
  // UI components and graphics
  uiComponents: {
    AA: 3,
  },
};

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Seth Hobson
Publisher
Seth Hobson (Community)
Source
wshobson/agents · plugins/ui-design/skills/accessibility-compliance · commit 4bde316
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Seth Hobson.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download accessibility-compliance.zip