Formulare semantisch korrekt strukturieren
A11Y
WCAG
Barrierefreiheit · WCAG 2.1 · ARIA · Formulare
Formulare semantisch korrekt strukturieren
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.

13 Min. Lesezeit label/for · fieldset/legend · ARIA · Checkout Magento 2.4.8 · Hyvä Theme · Alpine.js

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.

11. FAQ: Formulare semantisch korrekt strukturieren

1Warum reicht ein Platzhaltertext nicht als Label aus?
Placeholder verschwinden beim Fokussieren, haben oft zu geringen Kontrast und werden von manchen Screenreadern unzuverlässig vorgelesen. Ein echtes label bleibt dauerhaft verfügbar.
2Wie verbindet man label und Eingabefeld korrekt?
Über for/id-Zuordnung oder durch Verschachtelung des Eingabefelds im label-Element. Beide Varianten erzeugen dieselbe programmatische Verbindung.
3Wann muss ich fieldset und legend verwenden?
Bei zusammengehörigen Feldern wie einer Adresse oder bei Gruppen von Radiobuttons/Checkboxen. legend liefert den sonst nur visuellen Gruppenkontext.
4Wie kennzeichne ich Pflichtfelder barrierefrei?
required + aria-required="true" plus sichtbares Sternchen und zusätzlicher sr-only Text "(Pflichtfeld)" im Label.
5Was bewirken aria-invalid und aria-describedby?
aria-invalid signalisiert den Fehlerzustand, aria-describedby verweist auf die Fehlermeldung. Zusammen liest der Screenreader Status und Grund vor.
6Warum ist autocomplete wichtig für Barrierefreiheit?
Teil von WCAG 1.3.5, reduziert manuelle Eingaben deutlich, besonders wichtig für Nutzer mit motorischen oder kognitiven Einschränkungen.
7Darf ich den Fokusrahmen per outline: none entfernen?
Nur mit gleichwertigem Ersatz wie :focus-visible. Ersatzloses Entfernen verletzt WCAG 2.4.7 und macht Tastaturnutzung unmöglich nachvollziehbar.
8Wohin sollte der Fokus nach einem fehlgeschlagenen Absenden springen?
Zum ersten fehlerhaften Feld oder zur Fehlerzusammenfassung mit role="alert" am Formularanfang, damit niemand manuell suchen muss.
9Welche tabindex-Werte sind erlaubt?
Nur "0" und "-1". Positive Werte wie "1" reißen Felder aus der natürlichen DOM-Reihenfolge und sollten vermieden werden.
10Mit welchen Tools teste ich Formulare auf Barrierefreiheit?
Automatisiert mit axe DevTools oder WAVE, manuell mit NVDA oder VoiceOver, um die inhaltliche Verständlichkeit von Fehlermeldungen zu prüfen.