HTML-Formulare bauen: Felder, Validierung und Barrierefreiheit

HTML-Formulare bauen: Felder, Validierung und Barrierefreiheit

Ein Kontaktformular ist schnell zusammengeklickt und trotzdem eine der häufigsten Fehlerquellen einer Website: Felder ohne Beschriftung, Telefonnummern, die als Zahl abgelehnt werden, Fehlermeldungen nur in Rot, Spam im Postfach. Fast alles davon lässt sich mit Bordmitteln von HTML lösen, ohne zusätzliche Bibliothek. Dieser Beitrag geht die Bausteine in der Reihenfolge durch, in der man ein Formular baut, und endet mit einem vollständigen Kontaktformular zum Übernehmen.

Was sich seit HTML 4.01 geändert hat

HTML 4.01 kannte 1999 zehn Typen für input: text, password, checkbox, radio, submit, reset, file, hidden, image und button. Die Elemente label, fieldset, legend und button gab es bereits, eine eingebaute Prüfung der Eingaben dagegen nicht; dafür brauchte man JavaScript im onsubmit-Handler. Der heutige HTML Living Standard kennt über 20 Eingabetypen und eine Validierung direkt im Browser. Die Grundidee ist gleich geblieben: Jedes Feld hat einen name, und beim Absenden schickt der Browser die Paare aus Name und Wert an die Adresse im action-Attribut. Die übrigen Neuerungen gegenüber HTML 4.01 fasst der Beitrag zu HTML 4.01 zusammen.

Das Grundgerüst: form, label, fieldset und button

Jedes Feld braucht ein sichtbares label. Die W3C-Tutorials empfehlen die explizite Verknüpfung über for und id, weil Hilfstechnologien sie am zuverlässigsten erkennen. Ein Klick auf die Beschriftung setzt dann auch den Fokus ins Feld, was auf Touchscreens die Trefferfläche vergrößert. Ein placeholder ersetzt das Label nicht: MDN weist darauf hin, dass Screenreader Platzhalter standardmäßig nicht vorlesen, dass der Text kontrastarm dargestellt wird und beim Tippen verschwindet.

Zusammengehörige Felder wie Adresse oder eine Gruppe von Radiobuttons gehören in ein fieldset mit legend. Für den Absenden-Knopf ist <button type='submit'> die beste Wahl, weil er beliebigen Inhalt aufnehmen kann. Den Reset-Knopf sollte man weglassen; MDN rät ausdrücklich davon ab, und versehentlich gelöschte Eingaben ärgern jeden.

Den passenden Eingabetyp wählen

Der Typ bestimmt, welche Tastatur Mobilgeräte zeigen und was der Browser prüft. Nicht jede Ziffernfolge ist eine Zahl:

EingabeEmpfehlungBegründung
E-Mail-Adressetype='email'Formatprüfung und passende Tastatur
Telefonnummertype='tel'Telefontastatur, aber keine automatische Prüfung, weil Formate weltweit zu unterschiedlich sind
Webadressetype='url'Formatprüfung
Postleitzahl, Kundennummertype='text' mit inputmode='numeric'number ist laut MDN nur für echte Zahlen gedacht, nicht für Postleitzahlen oder Kartennummern
Menge, Anzahl Personentype='number' mit min/maxHoch- und Runterzählen ist hier sinnvoll
Datumtype='date'Datumsauswahl des Systems, Wert immer im Format JJJJ-MM-TT
Freitexttextareamehrzeilig, mit maxlength begrenzbar
Auswahl aus wenigen OptionenRadiobuttons im fieldsetalle Optionen auf einen Blick
Auswahl aus vielen Optionenselectplatzsparend, z. B. Länderliste

Eingebaute Validierung mit required, pattern, min und max

Mit required wird ein Feld zur Pflicht, minlength und maxlength begrenzen Textlängen, min, max und step Zahlen und Daten. Das Attribut pattern erwartet einen regulären Ausdruck, der den gesamten Wert treffen muss; der Browser setzt Anfang und Ende gedanklich davor und dahinter. Seit 2023 wird das Muster mit dem v-Flag ausgewertet, weshalb Zeichen wie - oder | in Zeichenklassen escaped werden müssen. Ein leeres Feld prüft pattern nicht, dafür braucht es zusätzlich required.

<label for='plz'>Postleitzahl</label>
<input id='plz' name='plz' type='text' inputmode='numeric'
       pattern='[0-9]{5}' autocomplete='postal-code'
       aria-describedby='plz-hinweis' required>
<span id='plz-hinweis'>Fünf Ziffern, z. B. 10115</span>

Das Format gehört als sichtbarer Hinweis neben das Feld, nicht nur in ein title-Attribut, das viele Nutzer nie zu sehen bekommen. Für die Gestaltung empfiehlt sich die Pseudoklasse :user-invalid statt :invalid: Sie greift erst, wenn jemand das Feld bearbeitet oder das Absenden versucht hat, sodass ein leeres Formular nicht schon beim Laden rot leuchtet. Laut MDN funktioniert sie seit November 2023 in allen großen Browsern.

autocomplete spart Tipparbeit

Mit dem Attribut autocomplete sagt man dem Browser, welche gespeicherten Daten in ein Feld gehören. Für Kontaktformulare sind die wichtigsten Werte name, given-name, family-name, email, tel, organization, street-address, postal-code und address-level2 (Ort). Das ist mehr als Komfort: WCAG 2.2 verlangt im Kriterium 1.3.5 »Identify Input Purpose« (Stufe AA), dass der Zweck von Feldern mit persönlichen Daten programmatisch bestimmbar ist, und autocomplete ist der übliche Weg dafür. Der Wert off ist laut Spezifikation für sensible oder nicht wiederverwendbare Daten gedacht, nicht für normale Adressfelder.

Fehlermeldungen, die alle verstehen

WCAG 2.2 verlangt auf Stufe A, dass automatisch erkannte Fehler benannt und in Text beschrieben werden (3.3.1) und dass Felder Beschriftungen oder Anleitungen haben (3.3.2). Die WAI-Tutorials des W3C empfehlen dazu:

  • Nach dem Absenden eine Fehlerliste über dem Formular, deren Einträge auf die betroffenen Felder verlinken.
  • Jede Meldung direkt am Feld, verknüpft per aria-describedby, mit einem Hinweis, wie man den Fehler behebt.
  • Den Fokus auf das erste fehlerhafte Feld setzen.
  • Text und Symbol statt nur roter Farbe, etwa »Fehler: Bitte eine E-Mail-Adresse mit @ angeben«.
  • Die Zahl der Fehler im Seitentitel nennen, etwa »2 Fehler – Kontakt«.

Die Fehlermeldungen des Browsers selbst sind ein brauchbarer Anfang, aber je nach Browser unterschiedlich formuliert und nicht gestaltbar. Für wichtige Formulare lohnt es sich, mit novalidate am form die Browser-Blasen abzuschalten und die Meldungen per Skript und Constraint Validation API selbst auszugeben.

Sicherheit, Datenschutz und Spam ohne Tracking

Die Prüfung im Browser ist eine Hilfe für ehrliche Nutzer, keine Sicherheitsmaßnahme. Jeder kann sie umgehen, deshalb muss der Server alle Eingaben erneut prüfen, Längen begrenzen und Werte beim Ausgeben escapen. Formulare gehören ausschließlich auf HTTPS-Seiten und sollten nur an HTTPS-Adressen senden. Datenschutzrechtlich zählen vor allem zwei Grundsätze aus Art. 5 Abs. 1 DSGVO: Datenminimierung (lit. c), also nur Felder abfragen, die wirklich gebraucht werden, und angemessene Sicherheit (lit. f). Ein Telefonfeld, das nie genutzt wird, ist überflüssig. Ob für ein konkretes Formular weitere Pflichten gelten, ist eine Frage für die Rechtsberatung.

Gegen Spam braucht ein kleines Kontaktformular selten ein Captcha eines großen Anbieters, das Daten an Dritte überträgt. In der Praxis reichen oft drei Maßnahmen auf dem Server: ein per CSS verstecktes Honeypot-Feld, das Menschen nicht sehen und Bots ausfüllen; eine Mindestzeit zwischen Seitenaufruf und Absenden; eine Begrenzung der Anfragen pro IP-Adresse und Stunde. Wer mehr braucht, findet Alternativen wie ALTCHA, das nach eigenen Angaben ohne Cookies und Fingerprinting mit einer Rechenaufgabe im Browser arbeitet und selbst gehostet werden kann.

Ein Kontaktformular zum Übernehmen

<form action='/kontakt' method='post'>
  <fieldset>
    <legend>Ihre Kontaktdaten</legend>
    <p>
      <label for='name'>Name</label>
      <input id='name' name='name' type='text' autocomplete='name' required>
    </p>
    <p>
      <label for='email'>E-Mail-Adresse</label>
      <input id='email' name='email' type='email' autocomplete='email' required>
    </p>
    <p>
      <label for='tel'>Telefon (optional)</label>
      <input id='tel' name='tel' type='tel' autocomplete='tel'>
    </p>
  </fieldset>
  <p>
    <label for='thema'>Thema</label>
    <select id='thema' name='thema' required>
      <option value=''>Bitte wählen</option>
      <option>Angebot</option>
      <option>Support</option>
      <option>Sonstiges</option>
    </select>
  </p>
  <p>
    <label for='nachricht'>Nachricht</label>
    <textarea id='nachricht' name='nachricht' rows='6'
              minlength='20' maxlength='2000' required></textarea>
  </p>
  <p class='hp'>
    <label for='website'>Bitte leer lassen</label>
    <input id='website' name='website' type='text' autocomplete='off'>
  </p>
  <p>
    <button type='submit'>Nachricht senden</button>
  </p>
</form>

Im Stylesheet blendet .hp { display: none; } das Honeypot-Feld aus; damit verschwindet es auch für Screenreader und aus der Tab-Reihenfolge. Der Server verwirft jede Nachricht, in der website ausgefüllt ist. Die leere erste Option im select sorgt zusammen mit required dafür, dass eine echte Auswahl getroffen werden muss. Wo das Formular auf der Seite platziert wird und wie Besucher es finden, ist eine Frage der Navigation.

Quellen