label, fieldset und Pflichtfelder ohne Screenreader-Fallen
Formulare sind der kritischste Berührungspunkt jeder Website für Menschen mit Screenreadern, denn ohne korrekte label-Zuordnung, sinnvolle fieldset- und legend-Gruppierung und eindeutig kommunizierte Pflichtfelder bleibt ein Checkout schlicht unbedienbar. Dieser Artikel zeigt anhand eines praktischen Adressformulars, wie Magento- und Hyvä-Entwickler Formulare semantisch korrekt aufbauen, Fehler barrierefrei kommunizieren und WCAG-konforme Eingabehilfen umsetzen.
Inhaltsverzeichnis
- 1. Warum semantische Formulare kein Nice-to-have sind
- 2. label/for: Die nicht verhandelbare Grundregel
- 3. fieldset und legend: Zusammengehörige Felder gruppieren
- 4. Pflichtfelder: Visuell und für Screenreader kennzeichnen
- 5. Fehlermeldungen und Validierung barrierefrei gestalten
- 6. Autocomplete und Eingabehilfen richtig nutzen
- 7. Tastaturbedienung und Fokus-Reihenfolge
- 8. Praxisbeispiel: Barrierefreies Checkout-Adressformular
- 9. Formulare im Vergleich: Typische Fehler und Lösungen
- 10. Zusammenfassung
- 11. FAQ
1. Warum semantische Formulare kein Nice-to-have sind
Formulare sind in jedem Onlineshop die Stelle, an der aus einem Besucher ein Kunde wird. Genau deshalb entscheidet die technische Struktur eines Formulars darüber, ob ein Nutzer mit Screenreader den Checkout überhaupt abschließen kann. Sehende Nutzer erkennen ein Eingabefeld an seiner visuellen Position neben einem Label, an einer Umrandung oder an Farbe. Ein Screenreader hat keinen Zugriff auf diese visuellen Signale und ist vollständig auf die programmatisch ausgezeichnete Struktur im HTML angewiesen. Fehlt diese Struktur, hört ein Nutzer nur „Eingabefeld, leer" ohne jeden Kontext, welches Feld gemeint ist.
WCAG 2.1 verlangt im Erfolgskriterium 1.3.1 (Info and Relationships), dass Struktur und Beziehungen zwischen Formularelementen programmatisch bestimmbar sind, nicht nur visuell erkennbar. Erfolgskriterium 3.3.2 (Labels or Instructions) verlangt zusätzlich, dass jedes Eingabefeld mit einer Beschriftung oder Anweisung versehen ist. Diese beiden Kriterien sind kein Nice-to-have, sondern die Grundvoraussetzung für ein bedienbares Formular. In Magento- und Hyvä-Themes betrifft das vor allem selbst gebaute Checkout-, Kontakt- und Kontoformulare, bei denen Tailwind-Klassen und Alpine.js schnell dazu verleiten, semantisches HTML zugunsten von Styling-Freiheit zu vernachlässigen.
2. label/for: Die nicht verhandelbare Grundregel
Die Verknüpfung von <label for="id"> mit dem passenden id-Attribut des zugehörigen Eingabefelds ist die wichtigste einzelne Regel für barrierefreie Formulare. Erst diese programmatische Zuordnung erlaubt es einem Screenreader, beim Fokussieren des Feldes den Labeltext vorzulesen, zum Beispiel „Vorname, Eingabefeld". Ohne diese Verbindung liest der Screenreader nur den Feldtyp vor, der Labeltext bleibt unverbunden im DOM stehen und wird schlimmstenfalls komplett ignoriert. Alternativ kann das Eingabefeld direkt im <label>-Element verschachtelt werden, was dieselbe programmatische Verbindung ohne explizites for-Attribut erzeugt.
Ein Platzhaltertext im placeholder-Attribut ersetzt niemals ein echtes Label. Placeholder verschwinden beim Fokussieren, haben oft zu geringen Farbkontrast und werden von manchen Screenreadern gar nicht oder nur unzuverlässig vorgelesen. Wo aus Layoutgründen kein sichtbares Label gewünscht ist, etwa bei einem einzelnen Suchfeld, bleibt das Label im DOM erhalten und wird visuell mit der Klasse sr-only ausgeblendet, statt komplett entfernt zu werden. So bleibt die programmatische Information für Screenreader vollständig erhalten, während sich am visuellen Erscheinungsbild nichts ändert.
<!-- WRONG: label has no programmatic connection to the input -->
<label>Vorname</label>
<input type="text" name="firstname" placeholder="Vorname eingeben">
<!-- WRONG: mismatched for/id values -->
<label for="firstname">Vorname</label>
<input type="text" id="vorname" name="firstname">
<!-- RIGHT: for/id match exactly -->
<label for="firstname" class="block text-sm font-medium text-gray-700 mb-1">
Vorname
</label>
<input type="text" id="firstname" name="firstname"
class="w-full rounded-lg border border-gray-300 px-3 py-2">
<!-- RIGHT: implicit association via nesting, no explicit for/id needed -->
<label class="flex items-center gap-2">
<input type="checkbox" name="newsletter">
<span>Newsletter abonnieren</span>
</label>
<!-- RIGHT: visible label hidden visually, still read by screen readers -->
<label for="search" class="sr-only">Produktsuche</label>
<input type="search" id="search" name="q" placeholder="Suchen...">
3. fieldset und legend: Zusammengehörige Felder gruppieren
Ein einzelnes Label beschreibt ein einzelnes Feld, aber viele Formulare bestehen aus Gruppen zusammengehöriger Felder, etwa Straße, Hausnummer, Postleitzahl und Stadt innerhalb einer Lieferadresse. Ohne zusätzliche Struktur springt ein Screenreader-Nutzer von Feld zu Feld, ohne zu wissen, dass er sich gerade innerhalb des Adressblocks „Lieferadresse" statt „Rechnungsadresse" befindet. Das Element <fieldset> mit einem verschachtelten <legend>-Element löst genau dieses Problem: Der Screenreader kündigt den Gruppentitel an, sobald der Nutzer in die Gruppe hineinnavigiert, und wiederholt ihn bei jedem einzelnen Feld als Kontext.
Besonders wichtig ist fieldset/legend bei Gruppen von Radiobuttons oder Checkboxen, etwa bei der Auswahl der Versandart. Ohne Gruppierung hört ein Nutzer nur „Radiobutton, DHL Standard", ohne die übergeordnete Frage „Versandart wählen" zu kennen. Für Adressblöcke gilt dieselbe Logik: Ein Formular mit getrennten fieldset-Gruppen für Rechnungs- und Lieferadresse verhindert Verwechslungen, die bei rein visueller Trennung durch Überschriften für Screenreader-Nutzer sonst unsichtbar bleiben. Zu tiefe Verschachtelung von fieldset-Elementen sollte vermieden werden, da sie die Navigation eher erschwert als erleichtert.
<!-- Shipping address as a properly grouped fieldset -->
<fieldset class="border border-gray-300 rounded-lg p-4 mb-6">
<legend class="px-2 font-semibold text-gray-800">Lieferadresse</legend>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mt-2">
<div class="sm:col-span-2">
<label for="ship-street" class="block text-sm font-medium text-gray-700 mb-1">Straße</label>
<input type="text" id="ship-street" name="ship_street" autocomplete="address-line1"
class="w-full rounded-lg border border-gray-300 px-3 py-2" required aria-required="true">
</div>
<div>
<label for="ship-housenumber" class="block text-sm font-medium text-gray-700 mb-1">Hausnummer</label>
<input type="text" id="ship-housenumber" name="ship_housenumber" autocomplete="address-line2"
class="w-full rounded-lg border border-gray-300 px-3 py-2" required aria-required="true">
</div>
</div>
</fieldset>
<!-- Radio group requires fieldset/legend, a plain heading is not enough -->
<fieldset class="border border-gray-300 rounded-lg p-4">
<legend class="px-2 font-semibold text-gray-800">Versandart wählen</legend>
<label class="flex items-center gap-2 mt-2">
<input type="radio" name="shipping_method" value="standard" checked>
<span>DHL Standard (2 bis 3 Werktage)</span>
</label>
<label class="flex items-center gap-2 mt-2">
<input type="radio" name="shipping_method" value="express">
<span>DHL Express (1 Werktag)</span>
</label>
</fieldset>
4. Pflichtfelder: Visuell und für Screenreader kennzeichnen
Ein rotes Sternchen neben dem Labeltext ist die gängige visuelle Konvention für Pflichtfelder, sagt einem Screenreader-Nutzer aber gar nichts, wenn das Sternchen nur als reines Sonderzeichen im Text steht. Manche Screenreader lesen das Zeichen als „Sternchen" vor, andere ignorieren es je nach Ausspracheeinstellung komplett. Verlässlich wird die Kennzeichnung erst durch die Kombination aus dem nativen HTML5-Attribut required und dem ARIA-Attribut aria-required="true". Moderne Screenreader kündigen bei fokussiertem Feld dann explizit „Pflichtfeld" oder „erforderlich" an, unabhängig vom visuellen Sternchen.
Zusätzliche Sicherheit bietet ein für Screenreader lesbarer, aber visuell ausgeblendeter Zusatztext direkt im Label, etwa <span class="sr-only">(Pflichtfeld)</span> nach dem sichtbaren Sternchen. So bleibt die visuelle Kennzeichnung kompakt, während Screenreader-Nutzer eine eindeutige Wortmeldung statt eines mehrdeutigen Symbols hören. Für optionale Felder in einem überwiegend pflichtigen Formular ist der umgekehrte Ansatz oft sinnvoller: Statt jedes Pflichtfeld zu markieren, wird nur das Wort „optional" bei den wenigen freiwilligen Feldern ergänzt, was die visuelle und akustische Informationsdichte insgesamt reduziert.
/* Required marker: visible asterisk plus screen reader announcement */
.form-label-required::after {
content: "*";
color: #dc2626;
margin-left: 0.25rem;
font-weight: 700;
}
/* Visually hidden but still announced by screen readers */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Focus indicator must stay visible, never remove without a replacement */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 2px solid #3f3f46;
outline-offset: 2px;
}
5. Fehlermeldungen und Validierung barrierefrei gestalten
Eine Fehlermeldung, die ausschließlich über eine rote Umrandung des Eingabefelds kommuniziert wird, existiert für einen Screenreader-Nutzer schlicht nicht. Barrierefreie Validierung braucht zwei programmatische Verbindungen: Das Attribut aria-invalid="true" am fehlerhaften Feld signalisiert den Fehlerzustand, und aria-describedby verweist auf die id der eigentlichen Fehlermeldung im Text. Erst durch diese Kombination liest der Screenreader beim Fokussieren des Feldes sowohl den Zustand „ungültige Eingabe" als auch den konkreten Grund vor, etwa „Postleitzahl muss aus 5 Ziffern bestehen".
Nach einem fehlgeschlagenen Absenden des Formulars sollte zusätzlich eine Fehlerzusammenfassung am Anfang des Formulars erscheinen, ausgezeichnet mit role="alert" oder in einem Bereich mit aria-live="polite". So erfahren Screenreader-Nutzer sofort, dass ein Fehler vorliegt, ohne das gesamte Formular erneut durchsuchen zu müssen. In Hyvä-Themes lässt sich dieses Muster gut mit Alpine.js umsetzen: Ein x-data-Objekt hält den Validierungszustand, setzt aria-invalid reaktiv und verschiebt den Tastaturfokus bei Bedarf gezielt zum ersten fehlerhaften Feld.
// Alpine.js form validation with accessible error announcements
document.addEventListener('alpine:init', () => {
Alpine.data('checkoutForm', () => ({
errors: {},
submitted: false,
validateField(field, value) {
if (field === 'postcode' && !/^\d{5}$/.test(value)) {
this.errors.postcode = 'Postleitzahl muss aus 5 Ziffern bestehen';
} else {
delete this.errors.postcode;
}
},
hasError(field) {
return Boolean(this.errors[field]);
},
async submitForm() {
this.submitted = true;
if (Object.keys(this.errors).length > 0) {
// Move focus to the error summary so screen reader users are not stranded
this.$refs.errorSummary?.focus();
return;
}
// proceed with actual submission
}
}));
});
6. Autocomplete und Eingabehilfen richtig nutzen
Das HTML-Attribut autocomplete mit standardisierten Werten wie given-name, family-name, street-address, postal-code oder country ist Teil von WCAG-Erfolgskriterium 1.3.5 (Identify Input Purpose) und erfüllt gleich zwei Zwecke. Für sehende Nutzer aktiviert es die Autofill-Funktion des Browsers und beschleunigt das Ausfüllen erheblich. Für Nutzer mit kognitiven Einschränkungen oder motorischen Behinderungen, die auf Spracheingabe oder Schalter-Steuerung angewiesen sind, reduziert es die Anzahl notwendiger manueller Eingaben drastisch, was einen direkten Zugänglichkeitsgewinn darstellt.
Ergänzend hilft das Attribut inputmode, auf mobilen Geräten die passende virtuelle Tastatur einzublenden, etwa inputmode="numeric" für Postleitzahlen oder inputmode="tel" für Telefonnummern, ohne dabei den semantischen type des Feldes zu verändern. Von autocomplete="off" sollte grundsätzlich abgesehen werden, außer bei sicherheitskritischen Feldern wie einem neuen Passwort, denn ein pauschal deaktiviertes Autofill zwingt alle Nutzer zur manuellen Eingabe, obwohl der Browser die Information bereits kennt.
7. Tastaturbedienung und Fokus-Reihenfolge
Jedes Formularfeld, jeder Button und jeder Link innerhalb eines Formulars muss allein mit der Tastatur erreichbar und bedienbar sein, ganz ohne Maus. Die Tab-Reihenfolge sollte der DOM-Reihenfolge und damit der visuellen Leserichtung entsprechen. Positive Werte im Attribut tabindex, etwa tabindex="1", reißen Felder aus dieser natürlichen Reihenfolge heraus und erzeugen eine für Screenreader-Nutzer völlig unvorhersehbare Navigation. Erlaubt sind ausschließlich tabindex="0", um ein von Natur aus nicht fokussierbares Element wie einen div in die Tab-Reihenfolge aufzunehmen, und tabindex="-1", um ein Element gezielt per JavaScript fokussierbar zu machen, ohne es in die Tab-Reihenfolge einzureihen.
Der sichtbare Fokusindikator, standardmäßig ein blauer Rahmen im Browser, darf niemals mit outline: none entfernt werden, ohne durch eine mindestens gleichwertige eigene Fokusdarstellung ersetzt zu werden. Genau dieser Fehler ist einer der häufigsten in individuell gestylten Formularen und verletzt WCAG-Erfolgskriterium 2.4.7 (Focus Visible). Nach einem gescheiterten Absenden des Formulars sollte der Fokus aktiv per JavaScript auf das erste fehlerhafte Feld oder die Fehlerzusammenfassung gesetzt werden, damit Tastatur- und Screenreader-Nutzer nicht selbst danach suchen müssen, wo im Formular das Problem liegt.
8. Praxisbeispiel: Barrierefreies Checkout-Adressformular
Die vorangegangenen Regeln greifen erst im Zusammenspiel: label/for-Zuordnung als Fundament, fieldset/legend zur Gruppierung der Lieferadresse, eine kombinierte visuelle und programmatische Pflichtfeld-Kennzeichnung sowie aria-invalid und aria-describedby für Fehlermeldungen. Das folgende Beispiel zeigt ein vollständiges Adressformular im Hyvä-typischen Aufbau mit Tailwind-Klassen und einer schlanken Alpine.js-Komponente für die clientseitige Validierung, ohne dass dabei ein einziges semantisches Element geopfert wird.
Wichtig ist, dass die Alpine.js-Logik ausschließlich zusätzliche ARIA-Attribute reaktiv setzt, aber niemals die grundlegende HTML-Struktur ersetzt. Jedes <script>-Inline-Element muss in Hyvä anschließend über $hyvaCsp->registerInlineScript() für die Content Security Policy freigegeben werden. So bleibt das Formular auch dann bedienbar, wenn JavaScript aus irgendeinem Grund nicht lädt, denn required und die native Formularvalidierung des Browsers funktionieren unabhängig von Alpine.js weiter.
<!-- phtml: accessible checkout shipping address form (Hyva Theme + Alpine.js) -->
<form
x-data="checkoutForm()"
@submit.prevent="submitForm()"
novalidate
class="max-w-xl"
>
<!-- Error summary, announced immediately after a failed submit -->
<div
x-show="submitted && Object.keys(errors).length > 0"
x-ref="errorSummary"
role="alert"
tabindex="-1"
class="mb-6 rounded-lg border border-red-300 bg-red-50 p-4 text-red-800"
>
<p class="font-semibold mb-1">Bitte korrigieren Sie die folgenden Felder:</p>
<ul class="list-disc list-inside text-sm">
<template x-for="(message, field) in errors" :key="field">
<li x-text="message"></li>
</template>
</ul>
</div>
<fieldset class="border border-gray-300 rounded-lg p-4 mb-6">
<legend class="px-2 font-semibold text-gray-800">Lieferadresse</legend>
<div class="mt-2">
<label for="ship-fullname" class="block text-sm font-medium text-gray-700 mb-1">
Vollständiger Name <span class="text-red-600" aria-hidden="true">*</span>
<span class="sr-only">(Pflichtfeld)</span>
</label>
<input
type="text" id="ship-fullname" name="ship_fullname"
autocomplete="name" required aria-required="true"
:aria-invalid="hasError('fullname') ? 'true' : 'false'"
:aria-describedby="hasError('fullname') ? 'ship-fullname-error' : null"
@blur="validateField('fullname', $event.target.value)"
class="w-full rounded-lg border border-gray-300 px-3 py-2"
>
<p x-show="hasError('fullname')" id="ship-fullname-error" x-text="errors.fullname"
class="mt-1 text-sm text-red-600"></p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mt-4">
<div class="sm:col-span-2">
<label for="ship-street" class="block text-sm font-medium text-gray-700 mb-1">
Straße <span class="text-red-600" aria-hidden="true">*</span>
<span class="sr-only">(Pflichtfeld)</span>
</label>
<input type="text" id="ship-street" name="ship_street" autocomplete="address-line1"
required aria-required="true" class="w-full rounded-lg border border-gray-300 px-3 py-2">
</div>
<div>
<label for="ship-housenumber" class="block text-sm font-medium text-gray-700 mb-1">
Hausnummer <span class="text-red-600" aria-hidden="true">*</span>
<span class="sr-only">(Pflichtfeld)</span>
</label>
<input type="text" id="ship-housenumber" name="ship_housenumber" autocomplete="address-line2"
required aria-required="true" class="w-full rounded-lg border border-gray-300 px-3 py-2">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mt-4">
<div>
<label for="ship-postcode" class="block text-sm font-medium text-gray-700 mb-1">
Postleitzahl <span class="text-red-600" aria-hidden="true">*</span>
<span class="sr-only">(Pflichtfeld)</span>
</label>
<input
type="text" id="ship-postcode" name="ship_postcode"
autocomplete="postal-code" inputmode="numeric" required aria-required="true"
:aria-invalid="hasError('postcode') ? 'true' : 'false'"
:aria-describedby="hasError('postcode') ? 'ship-postcode-error' : null"
@blur="validateField('postcode', $event.target.value)"
class="w-full rounded-lg border border-gray-300 px-3 py-2"
>
<p x-show="hasError('postcode')" id="ship-postcode-error" x-text="errors.postcode"
class="mt-1 text-sm text-red-600"></p>
</div>
<div class="sm:col-span-2">
<label for="ship-city" class="block text-sm font-medium text-gray-700 mb-1">
Stadt <span class="text-red-600" aria-hidden="true">*</span>
<span class="sr-only">(Pflichtfeld)</span>
</label>
<input type="text" id="ship-city" name="ship_city" autocomplete="address-level2"
required aria-required="true" class="w-full rounded-lg border border-gray-300 px-3 py-2">
</div>
</div>
<div class="mt-4">
<label for="ship-country" class="block text-sm font-medium text-gray-700 mb-1">
Land <span class="text-red-600" aria-hidden="true">*</span>
<span class="sr-only">(Pflichtfeld)</span>
</label>
<select id="ship-country" name="ship_country" autocomplete="country"
required aria-required="true" class="w-full rounded-lg border border-gray-300 px-3 py-2">
<option value="DE">Deutschland</option>
<option value="AT">Österreich</option>
<option value="CH">Schweiz</option>
</select>
</div>
</fieldset>
<button type="submit" class="rounded-lg bg-zinc-800 text-white font-semibold px-6 py-3">
Adresse speichern
</button>
</form>
Mironsoft
Barrierefreie Formulare, WCAG-Audits und Hyvä-Implementierung
Formulare, die wirklich jeder bedienen kann?
Wir prüfen eure Checkout- und Kontaktformulare auf label-Zuordnung, fieldset-Gruppierung, Pflichtfeld-Kennzeichnung und Fehlerbehandlung und setzen die Korrekturen direkt in Magento und Hyvä um.
Formular-Audit
Screenreader-Test mit NVDA und VoiceOver plus automatisierte axe-Analyse
Refactoring
label/for, fieldset/legend und aria-Attribute sauber nachrüsten
Hyvä-Implementierung
Barrierefreie Alpine.js-Komponenten für Validierung und Fehleranzeige
9. Formulare im Vergleich: Typische Fehler und Lösungen
Die folgende Übersicht stellt die häufigsten Zugänglichkeitsfehler in Formularen den jeweils barrierefreien Lösungen gegenüber. Jede Zeile beschreibt ein Muster, das in Magento- und Hyvä-Checkouts regelmäßig auftaucht, und zeigt, welche minimale Änderung ein Feld für Screenreader-Nutzer tatsächlich bedienbar macht.
| Aufgabe | Unzugänglich | Barrierefreie Lösung | Vorteil |
|---|---|---|---|
| Label-Zuordnung | placeholder statt Label |
<label for> mit passender id |
Screenreader liest den Feldnamen vor |
| Feldgruppe | Überschrift per div/h3 |
fieldset + legend |
Gruppenkontext wird bei jedem Feld angekündigt |
| Pflichtfeld | Nur rotes Sternchen im Text | required + aria-required + sr-only Text |
Eindeutige Sprachausgabe „Pflichtfeld" |
| Fehlermeldung | Nur rote Umrandung | aria-invalid + aria-describedby |
Fehlergrund wird vorgelesen |
| Fokusindikator | outline: none ohne Ersatz |
:focus-visible mit sichtbarem Rahmen |
Tastaturposition bleibt erkennbar |
| Tab-Reihenfolge | tabindex="1", "2", "3" |
DOM-Reihenfolge, tabindex="0"/"-1" |
Vorhersehbare Navigation |
In der Praxis lassen sich fast alle diese Muster automatisiert mit Tools wie axe DevTools oder WAVE aufspüren, bevor ein manueller Screenreader-Test überhaupt notwendig wird. Manuelles Testen mit NVDA unter Windows oder VoiceOver unter macOS bleibt trotzdem unverzichtbar, weil automatisierte Scanner zwar fehlende label-Zuordnungen zuverlässig erkennen, aber nicht beurteilen können, ob eine Fehlermeldung inhaltlich verständlich ist.
10. Zusammenfassung
Semantisch korrekte Formulare stehen und fallen mit wenigen, klar definierten Regeln: label und Eingabefeld über for/id programmatisch verbinden, zusammengehörige Felder in fieldset mit legend gruppieren, Pflichtfelder über required/aria-required plus sichtbarem und für Screenreader lesbarem Text kennzeichnen, und Fehler über aria-invalid/aria-describedby statt allein über Farbe kommunizieren. Jede dieser Regeln adressiert eine konkrete Lücke, die für sehende Nutzer unsichtbar bleibt, für Screenreader-Nutzer aber die Bedienbarkeit des gesamten Formulars entscheidet.
Der größte Hebel liegt darin, diese Regeln von Anfang an in Hyvä-Komponenten einzubauen, statt sie nachträglich in bestehende Checkout-Formulare zu flicken. Ein Adressformular, das label/for, fieldset/legend, Pflichtfeld-Kennzeichnung und Alpine.js-gestützte Fehlerbehandlung von Beginn an korrekt kombiniert, bleibt auch bei künftigen Erweiterungen wartbar und WCAG-konform, ohne dass jede neue Checkout-Variante erneut auf Barrierefreiheit geprüft werden muss.
Formulare semantisch korrekt strukturieren, das Wichtigste auf einen Blick
label/for
Jedes Eingabefeld braucht ein programmatisch verbundenes Label. Placeholder ersetzt niemals ein echtes label.
fieldset/legend
Zusammengehörige Felder wie eine Lieferadresse oder Radiobutton-Gruppen brauchen einen gemeinsamen Gruppenkontext.
Pflichtfelder
required + aria-required + sichtbarer und vorlesbarer Hinweistext statt nur eines Sternchens.
Fehler & Fokus
aria-invalid/aria-describedby für Fehlermeldungen, Fokus aktiv zum ersten Fehler verschieben.