Communitygithub.com

Frontend-A11y (Barrierefreiheit)

Praktische Barrierefreiheitsmuster für React und Next.js. Deckt die häufigsten Probleme aus Code-Reviews ab: fehlende Formular-Labels, falsche ARIA-Nutzung, nicht-semantische interaktive Elemente und defekte Tastaturnavigation.

Was ist Frontend-A11y (Barrierefreiheit)?

Diese Fähigkeit (Skill) bietet eine umfassende Referenz für die Implementierung von Barrierefreiheit in React- und Next.js-Anwendungen. Sie behandelt die in Code-Reviews am häufigsten beanstandeten Probleme: fehlende Verknüpfungen von Formular-Labels (`htmlFor`/`id`), falsche oder fehlende ARIA-Attribute, nicht-semantische HTML-Elemente (z. B. `<div>` mit `onClick`), defekte Tastaturnavigation und fehlendes Fokus-Management. Der Skill ist in die Bereiche Formularzugänglichkeit, semantisches HTML, ARIA-Attribute, Tastaturnavigation, Fokus-Management (inkl. Fokus-Wiederherstellung bei Modalen), Bilder/Icons, reduzierte Bewegung (`prefers-reduced-motion`) und Anti-Patterns unterteilt. Er liefert direkt kopierbare Codebeispiele für gängige Komponenten wie Formulare, Dropdowns, Modale und Akkordeons. Der Skill richtet sich an Entwickler, die Komponenten bauen oder reviewen und dabei sicherstellen wollen, dass sie von Screenreadern und per Tastatur bedienbar sind. Er unterstützt die gängigen Code-Review-Tools und ESLint-Regeln für Barrierefreiheit.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/frontend-a11y

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Vorschau

Aus dem Skill-README

Frontend-A11y (Barrierefreiheit) preview
Frontend-A11y (Barrierefreiheit) preview
Frontend-A11y (Barrierefreiheit) preview

Dokumentation

Frontend-Barrierefreiheitsmuster (A11y)

Praktische Barrierefreiheitsmuster für React und Next.js. Behandelt die Probleme, die in Code-Reviews am häufigsten beanstandet werden: fehlende Formular-Labels, falsche ARIA-Nutzung, nicht-semantische interaktive Elemente und defekte Tastaturnavigation.

Wann aktivieren

  • Erstellen oder Überprüfen von Formularkomponenten (<input>, <select>, <textarea>)
  • Erstellen interaktiver Elemente (Modale, Dropdowns, Tooltips, Tabs)
  • Verwenden von <div> oder <span> mit onClick
  • Hinzufügen von aria-* Attributen zu einem beliebigen Element
  • Implementieren der Tastaturnavigation oder des Fokus-Managements
  • Erhalten von Barrierefreiheits-Feedback durch Code-Review-Tools (CodeRabbit, ESLint a11y)
  • Erstellen von Komponenten, die Screenreader unterstützen müssen

Formularzugänglichkeit

Fehlende Verknüpfung von htmlFor / id und nicht verbundene Fehlermeldungen sind die häufigsten Probleme in Code-Reviews.

Label-Verbindung

// SCHLECHT: Label hat keine Verbindung zum Input – Screenreader können sie nicht zuordnen
<label>E-Mail</label>
<input type="email" />

// GUT: htmlFor stimmt mit der Input-ID überein
<label htmlFor="email">E-Mail</label>
<input id="email" type="email" />

Pflichtfelder

// SCHLECHT: Nur visuelles Sternchen, kein Screenreader-Hinweis
<label htmlFor="email">E-Mail *</label>
<input id="email" type="email" />

// GUT: required aktiviert native Browser-Validierung; aria-required signalisiert es Screenreadern
<label htmlFor="email">
  E-Mail <span aria-hidden="true">*</span>
</label>
<input id="email" type="email" required aria-required="true" />

Fehlermeldungen

// SCHLECHT: Fehlertext ist visuell vorhanden, aber nicht mit dem Input verknüpft
<input id="email" type="email" />
<span className="error">Ungültige E-Mail-Adresse</span>

// GUT: aria-describedby verbindet den Input mit seiner Fehlermeldung
// aria-invalid signalisiert den ungültigen Zustand an Screenreader
<input
  id="email"
  type="email"
  aria-describedby="email-error"
  aria-invalid={!!error}
/>
{error && (
  <span id="email-error" role="alert">
    {error}
  </span>
)}

Vollständiges barrierefreies Formular

interface LoginFormProps {
  onSubmit: (email: string, password: string) => void;
}

export function LoginForm({ onSubmit }: LoginFormProps) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState<{ email?: string; password?: string }>({});

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const newErrors: typeof errors = {};
    if (!email) newErrors.email = 'E-Mail ist erforderlich';
    if (!password) newErrors.password = 'Passwort ist erforderlich';
    if (Object.keys(newErrors).length) {
      setErrors(newErrors);
      return;
    }
    onSubmit(email, password);
  };

  return (
    <form onSubmit={handleSubmit} noValidate>
      <div>
        <label htmlFor="email">
          E-Mail <span aria-hidden="true">*</span>
        </label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
          aria-required="true"
          aria-describedby={errors.email ? 'email-error' : undefined}
          aria-invalid={!!errors.email}
          autoComplete="email"
        />
        {errors.email && (
          <span id="email-error" role="alert">
            {errors.email}
          </span>
        )}
      </div>

      <div>
        <label htmlFor="password">
          Passwort <span aria-hidden="true">*</span>
        </label>
        <input
          id="password"
          type="password"
          value={password}
          onChange={e => setPassword(e.target.value)}
          aria-required="true"
          aria-describedby={errors.password ? 'password-error' : undefined}
          aria-invalid={!!errors.password}
          autoComplete="current-password"
        />
        {errors.password && (
          <span id="password-error" role="alert">
            {errors.password}
          </span>
        )}
      </div>

      <button type="submit">Anmelden</button>
    </form>
  );
}

Semantisches HTML

Verwende das Element, das der Absicht entspricht. Screenreader und Tastaturnutzer sind auf native Semantik angewiesen.

// SCHLECHT: div hat keine Rolle, keine Tastaturunterstützung, keinen zugänglichen Namen
<div onClick={handleClick}>Absenden</div>

// GUT: button ist fokussierbar, aktiviert bei Eingabe/Leertaste, wird als "Schaltfläche" angekündigt
<button type="button" onClick={handleClick}>Absenden</button>
// SCHLECHT: nicht-semantische Navigation
<div onClick={() => navigate('/home')}>Startseite</div>

// GUT: Anchor unterstützt Rechtsklick, Mittelklick und Tastaturnavigation
<a href="/home">Startseite</a>
// SCHLECHT: Überschriften-Hierarchie übersprungen (h1 zu h4)
<h1>Dashboard</h1>
<h4>Letzte Aktivität</h4>

// GUT: aufeinanderfolgende Überschriftenebenen
<h1>Dashboard</h1>
<h2>Letzte Aktivität</h2>

ARIA-Attribute

Verwende ARIA nur, wenn native HTML-Semantik nicht ausreicht. Falsches ARIA ist schlechter als kein ARIA.

aria-label vs. aria-labelledby

// aria-label: Inline-String-Label – verwende, wenn kein sichtbarer Labeltext existiert
<button aria-label="Modal schließen">
  <XIcon />
</button>

// aria-labelledby: Referenziert den Text eines anderen Elements – verwende, wenn ein sichtbares Label existiert
<section aria-labelledby="section-title">
  <h2 id="section-title">Letzte Bestellungen</h2>
  {/* Inhalt */}
</section>

aria-describedby

// Stellt eine ergänzende Beschreibung über das Label hinaus bereit
<button
  aria-describedby="delete-warning"
  onClick={handleDelete}
> Account löschen
</button>
<p id="delete-warning">Diese Aktion kann nicht rückgängig gemacht werden.</p>

aria-live für dynamische Inhalte

// Verwende aria-live, um Inhalte anzukündigen, die ohne Seitenneuladen aktualisiert werden
// polite: wartet, bis der Benutzer die aktuelle Aktion beendet hat, bevor angekündigt wird
// assertive: unterbricht sofort – nur für dringende Fehler verwenden

export function StatusMessage({ message, isError }: { message: string; isError?: boolean }) {
  return (
    <div role="status" aria-live={isError ? 'assertive' : 'polite'} aria-atomic="true">
      {message}
    </div>
  );
}

aria-expanded und aria-controls

export function Accordion({ title, children }: { title: string; children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(false);
  const contentId = useId();

  return (
    <div>
      <button aria-expanded={isOpen} aria-controls={contentId} onClick={() => setIsOpen(prev => !prev)}>
        {title}
      </button>
      <div id={contentId} hidden={!isOpen}>
        {children}
      </div>
    </div>
  );
}

Tastaturnavigation

Jedes interaktive Element muss allein mit der Tastatur erreichbar und bedienbar sein.

Benutzerdefiniertes Dropdown

export function Dropdown({ options, onSelect }: { options: string[]; onSelect: (value: string) => void }) {
  const [isOpen, setIsOpen] = useState(false);
  const [activeIndex, setActiveIndex] = useState(0);
  const listId = useId();

  if (!options.length) return null;

  const handleKeyDown = (e: React.KeyboardEvent) => {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setActiveIndex(i => Math.min(i + 1, options.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setActiveIndex(i => Math.max(i - 1, 0));
        break;
      case 'Enter':
      case ' ':
        e.preventDefault();
        if (isOpen) onSelect(options[activeIndex]);
        setIsOpen(prev => !prev);
        break;
      case 'Escape':
        setIsOpen(false);
        break;
    }
  };

  return (
    <div
      role="combobox"
      aria-expanded={isOpen}
      aria-haspopup="listbox"
      aria-controls={listId}
      tabIndex={0}
      onKeyDown={handleKeyDown}
      onClick={() => setIsOpen(prev => !prev)}
    >
      <span>{options[activeIndex]}</span>
      {isOpen && (
        <ul id={listId} role="listbox">
          {options.map((option, index) => (
            <li
              key={option}
              role="option"
              aria-selected={index === activeIndex}
              onClick={() => {
                onSelect(option);
                setIsOpen(false);
              }}
            >
              {option}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Fokus-Management

Der Fokus muss sich bei Änderungen des UI-Zustands logisch bewegen – insbesondere bei Modalen und Routenübergängen.

Fokus-Wiederherstellung bei Modalen

Dieses Beispiel behandelt den initialen Fokus und die Wiederherstellung. Für eine vollständige Fokusfalle (Tab/Umschalt+Tab-Zyklus innerhalb des Modals) verwende eine Bibliothek wie focus-trap-react, die Randfälle wie dynamische Inhalte und verschachtelte Portale behandelt.

export function Modal({ isOpen, onClose, title, children }: { isOpen: boolean; onClose: () => void; title: string; children: React.ReactNode }) {
  const modalRef = useRef<HTMLDivElement>(null);
  const previousFocusRef = useRef<HTMLElement | null>(null);

  useEffect(() => {
    if (isOpen) {
      // Aktuell fokussiertes Element speichern und Fokus in das Modal verschieben
      previousFocusRef.current = document.activeElement as HTMLElement;
      modalRef.current?.focus();
    } else {
      // Fokus auf das Element zurückgeben, das das Modal geöffnet hat
      previousFocusRef.current?.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div ref={modalRef} role="dialog" aria-modal="true" aria-labelledby="modal-title" tabIndex={-1} onKeyDown={e => e.key === 'Escape' && onClose()}>
      <h2 id="modal-title">{title}</h2>
      {children}
      <button onClick={onClose}>Schließen</button>
    </div>
  );
}

Bilder und Symbole

// SCHLECHT: Dekoratives Symbol wird als unbeschriftetes Bild angekündigt
<img src="/icon.svg" />

// GUT: Dekoratives Bild wird vor Screenreadern versteckt
<img src="/decoration.png" alt="" aria-hidden="true" />

// GUT: Bedeutungsvolles Bild mit beschreibendem Alt-Text
<img src="/chart.png" alt="Monatlicher Umsatz stieg von Januar bis März um 23 %" />

// GUT: Symbol-Schaltfläche mit zugänglichem Label
<button aria-label="Element löschen">
  <TrashIcon aria-hidden="true" />
</button>

Reduzierte Bewegung

Respektiere Benutzer, die in ihren Betriebssystemeinstellungen reduzierte Bewegung angefordert haben.

export function useReducedMotion(): boolean {
  const [prefersReduced, setPrefersReduced] = useState(false);

  useEffect(() => {
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    setPrefersReduced(mq.matches);
    const handler = (e: MediaQueryListEvent) => setPrefersReduced(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, []);

  return prefersReduced;
}

// Verwendung
export function AnimatedCard({ children }: { children: React.ReactNode }) {
  const reduceMotion = useReducedMotion();

  return (
    <div
      style={{
        transition: reduceMotion ? 'none' : 'transform 300ms ease'
      }}
    >
      {children}
    </div>
  );
}

Anti-Patterns

// SCHLECHT: onClick auf nicht-interaktivem Element ohne Tastaturunterstützung
<div onClick={handleClick}>Klick mich</div>

// SCHLECHT: aria-label auf einem div ohne Rolle
<div aria-label="Navigation">...</div>

// SCHLECHT: placeholder als Ersatz für Label verwendet
<input placeholder="Geben Sie Ihre E-Mail ein" />

// SCHLECHT: positiver tabIndex erzeugt unvorhersehbare Tab-Reihenfolge
<button tabIndex={3}>Absenden</button>

// SCHLECHT: aria-hidden auf einem fokussierbaren Element – Tastaturnutzer werden gefangen
<button aria-hidden="true">Öffnen</button>

// SCHLECHT: role="button" auf div ohne Tastatur-Handler
<div role="button" onClick={handleClick}>Absenden</div>
// Fehlt: tabIndex={0}, onKeyDown für Eingabe/Leertaste

Checkliste

Bevor du eine interaktive Komponente zum Review einreichst:

  • Jeder <input>, <select> und <textarea> hat ein verbundenes <label> über htmlFor/id
  • Fehlermeldungen sind mit aria-describedby verknüpft und mit role="alert" gekennzeichnet
  • Kein onClick auf <div> oder <span> ohne role, tabIndex und onKeyDown
  • Symbol-Schaltflächen haben aria-label
  • Dekorative Bilder verwenden alt="" und aria-hidden="true"
  • Modale stellen den Fokus beim Schließen wieder her (für vollständige Fokusfallen mit Tab/Umschalt+Tab-Zyklus eine Bibliothek wie focus-trap-react verwenden)
  • Dynamische Inhaltsaktualisierungen verwenden aria-live
  • prefers-reduced-motion wird bei Animationen beachtet

Verwandte Fähigkeiten

  • frontend-patterns – allgemeine React-Komponenten- und Zustandsmuster
  • design-system – Design-Token und Komponenten-Konsistenz
  • motion-foundations und motion-patterns: Animationsmuster mit Barrierefreiheitsaspekten

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Verwandte Skills