Kontakt-Formular BFSG-konform: eine Schritt-für-Schritt-Anleitung für Web-Entwickler
Informationen zum Artikel

Autor: Dmitry Dugarev
Das Barrierefreiheitsstärkungsgesetz (BFSG) [1] ist in Kraft und rückt die digitale Barrierefreiheit in den Fokus. Für Dich als Entwickler heißt das: Jedes Element, besonders ein Kontaktformular, muss für absolut jeden bedienbar sein. Ein unzugängliches Formular ist nicht nur ein rechtliches Risiko, sondern auch eine verpasste Chance, mit potenziellen Kunden in Kontakt zu treten [2].
In dieser Anleitung bauen wir Schritt für Schritt ein Formular, das den aktuellen gesetzlichen Anforderungen entspricht. Ich erkläre Dir nicht nur, was zu tun ist, sondern auch, warum und mit entsprechenden Quellangaben auf offizielle Richtlinien. Am Ende hast Du ein robustes, barrierefreies Kontaktformular, das Du in Deinen Projekten einsetzen kannst.
Hier ist die Live-Demo des fertigen, barrierefreien Kontakt-Formulars:
Grundstruktur & Anweisungen
Die Basis jedes barrierefreien Formulars ist sauberes, semantisches HTML. Das fängt bei der korrekten Verknüpfung von Beschriftungen (<label>) mit ihren Eingabefeldern (<input>) an.
Jedes Feld braucht ein permanent sichtbares <label> [3], das über das for-Attribut mit der id des Feldes verbunden ist [4]. Platzhalter, die bei der Eingabe verschwinden, sind kein Ersatz für ein Label [5]. Auf der Abbildung 1.1 siehst Du die Beispielstruktur eines barrierefreien Formulars, den wir im Folgenden aufbauen werden.
Außerdem solltest Du allgemeine Anweisungen, wie die Kennzeichnung von Pflichtfeldern, vor dem Formular platzieren, damit Nutzer wissen, was sie erwartet [6]. Die Labels der Eingabefelder sollen ebenfalls vor dem Feld stehen [7].
Textbeschreibung für "Schematische Grundstruktur eines barrierefreien Formulars" öffnen
Dieses Diagramm zeigt die Grundbausteine eines Formulars.
- Kontakt-Formular: Das äußerste Element, das alles umschließt. Es enthält Anweisungen, eine Fehlerübersicht, Fieldsets und einen Sende-Button.
- Feldgruppen (
<fieldset>): Dient zur Gruppierung der verwandten Eingabefelder. Es enthält eine Beschriftung (<legend>) und die eigentlichen Eingabefelder. - Eingabefelder: Jedes Feld besteht aus einer Beschriftung (
<label>), die mit einem Eingabefeld (<input>) verknüpft ist. Zusätzlich kann es eine Eingabehilfe geben, die weitere Informationen zum Feld bereitstellt.
Hier ist ein Codebeispiel für die Grundstruktur:
<p id="form-instructions-demo">
Alle mit einem Sternchen (*) markierten Felder sind Pflichtfelder.
</p>
<div
id="error-summary-demo"
class="contact-form__error-summary"
role="alert"
tabIndex="-1"
hidden
aria-labelledby="error-summary-title-demo"
>
<h2 id="error-summary-title-demo">Ihre Eingaben sind fehlerhaft</h2>
<p id="error-summary-intro"></p>
<ul class="contact-form__error-summary-list">
</ul>
</div>
<form id="contact-form-demo" class="contact-form__form" novalidate>
<div class="contact-form__group">
<label for="fname-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Fehler: </span>
Vorname*
</label>
<input
type="text"
id="fname-demo"
name="fname"
required
aria-required="true"
aria-invalid="false"
aria-describedby="fname-error-demo"
class="contact-form__input"
/>
<div
id="fname-error-demo"
class="contact-form__error-message"
hidden
></div>
</div>
<button type="submit" class="contact-form__submit-button">
Nachricht senden
</button>
</form>
Wir verwenden novalidate im <form>-Tag, um die unzugängliche Standard-Validierung des Browsers zu deaktivieren und die Fehlerbehandlung selbst zu steuern.
Fokus-Reihenfolge und Sichtbarkeit für Tastaturbedienbarkeit
Ein Formular, das man nicht ohne Maus bedienen kann, ist nicht barrierefrei. Zwei Kriterien sind hier essenziell:
-
Logische Fokus-Reihenfolge: Nutzer, die mit der
Tab-Taste navigieren, müssen sich in einer logischen und intuitiven Reihenfolge durch das Formular bewegen können [9]. Bei einer sauberen HTML-Struktur (von oben nach unten) ist dies meist automatisch gegeben. Komplexere Layouts (z.B. mit CSS Grid oder Flexbox) müssen daraufhin geprüft werden. -
Sichtbarer Fokus: Jedes interaktive Element (Input, Button, Link) muss einen deutlich sichtbaren Indikator zeigen, wenn es den Fokus erhält [10]. Dies ist der "Sie sind hier"-Indikator für Tastaturnutzer. Verstecke niemals den
outline-Stil, ohne einen besseren, deutlich sichtbaren Ersatz zu bieten.
Logische Gruppierung der Eingabefelder
Zusammengehörige Informationen (wie "Vorname" und "Nachname" oder eine Gruppe von Radio-Buttons) müssen auch programmatisch als Gruppe erkennbar sein [11]. Dafür nutzt Du <fieldset> und <legend> [12] oder alternativ ARIA-Rollen wie role="group" [13].
Das <fieldset> umschließt die Gruppe, und die <legend> gibt ihr einen Namen, den Screenreader vorlesen [12]. Ohne diese Struktur müssen Nutzer assistiver Technologien raten, welche Felder einen thematischen Block bilden.
<fieldset class="contact-form__fieldset">
<legend class="contact-form__legend">Ihre persönlichen Daten</legend>
<div class="contact-form__group">
<label for="fname-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Fehler: </span>
Vorname*
</label>
<input
type="text"
id="fname-demo"
name="fname"
required
aria-required="true"
aria-invalid="false"
aria-describedby="fname-error-demo"
class="contact-form__input"
/>
<div id="fname-error-demo" class="contact-form__error-message" hidden></div>
</div>
<div class="contact-form__group">
<label for="lname-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Fehler: </span>
Nachname*
</label>
<input
type="text"
id="lname-demo"
name="lname"
required
aria-required="true"
aria-invalid="false"
aria-describedby="lname-error-demo"
class="contact-form__input"
/>
<div id="lname-error-demo" class="contact-form__error-message" hidden></div>
</div>
</fieldset>
<fieldset
class="contact-form__fieldset"
aria-describedby="contactMethod-group-error-demo"
>
<legend id="contactMethod-legend" class="contact-form__legend">
<span class="contact-form__error-prefix" hidden>Fehler: </span>
Bevorzugte Kontaktmethode*
</legend>
<div class="contact-form__radio-group">
<label for="contact-pref-email-demo" class="contact-form__radio-label">
<input
type="radio"
id="contact-pref-email-demo"
name="contactMethod"
value="email"
required
aria-required="true"
class="contact-form__radio-input"
/>
Per E-Mail
</label>
<label for="contact-pref-phone-demo" class="contact-form__radio-label">
<input
type="radio"
id="contact-pref-phone-demo"
name="contactMethod"
value="phone"
required
aria-required="true"
class="contact-form__radio-input"
/>
Per Telefon
</label>
</div>
<div
id="contactMethod-group-error-demo"
class="contact-form__error-message"
hidden
></div>
</fieldset>
Hilfetexte & Eingabe-Unterstützung
Manchmal brauchen Felder zusätzliche Erklärungen, z.B. zu einem bestimmten Format. Diese Hilfetexte müssen programmatisch mit dem Feld verknüpft werden.
Dafür bekommt der Hilfetext eine id und das <input>-Element ein aria-describedby-Attribut, das auf diese id verweist [14]. Ein Screenreader liest dann zuerst das Label und anschließend den Hilfetext vor.
Um die Eingabe weiter zu erleichtern, solltest Du das autocomplete-Attribut verwenden [15]. Es erlaubt dem Browser, bekannte Daten (Name, E-Mail, etc.) automatisch vorzuschlagen. Das ist nicht nur komfortabel, sondern eine explizite Anforderung [16].
<div class="contact-form__group">
<label for="phone-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Fehler: </span>
Handynummer (Optional)
</label>
<input
type="tel"
id="phone-demo"
name="phone"
autocomplete="tel"
aria-invalid="false"
aria-describedby="phone-hint-demo phone-error-demo"
class="contact-form__input"
placeholder="z.B.: 01701234567"
/>
<span id="phone-hint-demo" class="contact-form__help-text">
Für eventuelle Rückfragen. Nur gültige deutsche Handynummern.
</span>
<div id="phone-error-demo" class="contact-form__error-message" hidden></div>
</div>
<div class="contact-form__group">
<label for="email-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Fehler: </span>
E-Mail-Adresse*
</label>
<input
type="email"
id="email-demo"
name="email"
autocomplete="email"
required
aria-required="true"
aria-invalid="false"
aria-describedby="email-hint-demo email-error-demo"
class="contact-form__input"
/>
<span id="email-hint-demo" class="contact-form__help-text">
Eine gültige E-Mail-Adresse (z.B. max.mustermann@example.com).
</span>
<div id="email-error-demo" class="contact-form__error-message" hidden></div>
</div>
Fehlerbehandlung & Validierung
Fehler passieren. Ein barrierefreies Formular leitet den Nutzer präzise zur Korrektur an [18]. Die Standard-Validierung des Browsers (die wir mit novalidate deaktiviert haben) ist hierfür ungeeignet, da ihre Meldungen oft nicht fokussierbar sind oder von Screenreadern nicht wahrgenommen werden [19].
Unser Ansatz besteht aus drei Teilen:
- Fehlerzusammenfassung: Ein
<div>am Anfang des Formulars, das alle Fehler auflistet [6]. Das hilft Nutzern, schnell zu sehen, was korrigiert werden muss und ermöglicht es, direkt zu den fehlerhaften Feldern zu springen [20]. - Inline-Fehler: Eine Textmeldung direkt unter dem fehlerhaften Feld, um dem Nutzer ausführliche Informationen zur Verfügung zu stellen [21]. Das Eingabefeld selbst wird mit
aria-invalid="true"markiert, damit Screenreader den Fehler erkennen [22]. - Fokusmanagement: Der Fokus wird bei einem Fehler per JavaScript aktiv auf die Fehlerzusammenfassung gesetzt.
- Wir passen das
<label>(oder die<legend>) jedes fehlerhaften Feldes an, indem wir ein "Fehler: "-Präfix hinzufügen. Dies ist entscheidend, um WCAG-Erfolgskriterium 1.4.1 [23] zu erfüllen. Wenn wir nur die Farbe ändern würden, wäre der Fehler für farbenblinde Nutzer nicht erkennbar.
Auf der Abbildung 4.1 siehst Du die Logik der Validierung und Fehlerbehandlung, die wir im Folgenden implementieren werden.
Textbeschreibung für "Logik der barrierefreien Fehlerbehandlung" öffnen
Dieses Ablaufdiagramm zeigt den Validierungsprozess:
- Der Benutzer klickt auf "Senden".
- JavaScript verhindert das Standard-Senden.
- Die
resetErrors()-Funktion löscht alle alten Fehlermeldungen. - Die
validateForm()-Funktion prüft alle Felder. - Eine Entscheidung prüft: "Fehler gefunden?".
- Wenn Ja: Die
showErrorSummary()-Funktion füllt die Fehlerzusammenfassung,showInlineError()markiert die Felder, und der Fokus wird pererrorSummary.focus()auf die Fehlerzusammenfassung gesetzt. - Wenn Nein: Das Formular wird abgesendet.
Jetzt bauen wir die Logik und die HTML-Struktur für die Fehlerbehandlung.
- HTML für Fehlerzusammenfassung
- JavaScript für die Validierung
Die Fehlerzusammenfassung müssen im HTML vorbereitet sein. Die Zusammenfassung erhält role="alert", damit Screenreader sie bei Einblendung sofort vorlesen [25], und tabindex="-1", damit wir sie per JavaScript fokussieren können [26].
<div
id="error-summary-demo"
class="contact-form__error-summary"
role="alert"
tabindex="-1"
hidden
aria-labelledby="error-summary-title-demo"
>
<h2 id="error-summary-title-demo">Ihre Eingaben sind fehlerhaft</h2>
<p id="error-summary-intro"></p>
<ul class="contact-form__error-summary-list"></ul>
</div>
<!-- Formularfelder -->
In diesem Beispiel ist die Fehlerzusammenfassung zunächst mit hidden und aria-hidden="true" ausgeblendet. Sobald ein Fehler auftritt, wird sie per JavaScript sichtbar gemacht.
Das Script fängt das submit-Event ab, validiert die Felder und steuert die Anzeige der Fehler.
Wichtig für die Barrierefreiheit sind hier zwei Dinge:
showErrorSummary(errors): Baut die Fehlerliste mit Links auf, die direkt zum fehlerhaften Feld springen. Das ist essenziell für die Tastaturnavigation [20].showInlineError(inputId, message): Setzt das Attributaria-invalid="true"am fehlerhaften<input>[22] und blendet die Inline-Meldung ein.
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form-demo');
const fnameInput = document.getElementById('fname-demo');
const errorSummary = document.getElementById('error-summary-demo');
const errorSummaryList = errorSummary.querySelector(
'.contact-form__error-summary-list'
);
const errorSummaryIntro = document.getElementById('error-summary-intro');
// --- Regex-Definitionen ---
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// --- Feld-Konfiguration (vereinfacht für das Snippet) ---
// Das Original-Skript verwendet ein "fieldConfig"-Objekt,
// um alle DOM-Elemente (Inputs, Labels, Fehler-Divs) zu verwalten.
// Hier greifen wir der Einfachheit halber direkt zu.
form.addEventListener('submit', (event) => {
event.preventDefault();
resetErrors(); // Alte Fehler zurücksetzen
const newErrors = validateForm(); // Formular validieren
if (Object.keys(newErrors).length > 0) {
displayErrors(newErrors); // Neue Fehler anzeigen
errorSummary.focus(); // Fokus auf Zusammenfassung setzen
} else {
// Formular erfolgreich senden
console.log('Formular ist valide.');
// (Logik wird im nächsten Abschnitt hinzugefügt)
}
});
/**
* Liest das Formular aus und gibt ein Fehler-Objekt zurück.
*/
function validateForm() {
const newErrors = {};
// (Hier die Logik für alle Felder einfügen)
if (fnameInput.value.trim() === '') {
newErrors.fname = 'Bitte geben Sie Ihren Vornamen an.';
}
// ...
const emailInput = document.getElementById('email-demo');
if (emailInput.value.trim() === '') {
newErrors.email = 'Bitte geben Sie Ihre E-Mail-Adresse an.';
} else if (!EMAIL_REGEX.test(emailInput.value)) {
newErrors.email = 'Bitte geben Sie eine gültige E-Mail-Adresse ein.';
}
// ...
return newErrors;
}
/**
* Zeigt alle Fehler in der Zusammenfassung und Inline an.
*/
function displayErrors(currentErrors) {
errorSummaryList.innerHTML = ''; // Alte Fehler-Links löschen
const errorEntries = Object.entries(currentErrors);
errorSummaryIntro.textContent = `Bitte korrigieren Sie die folgenden ${errorEntries.length} Fehler:`;
errorEntries.forEach(([key, message]) => {
// ID des Feldes (z.B. 'fname-demo')
const fieldId = key + '-demo';
// Inline-Fehler anzeigen
const errorElement = document.getElementById(fieldId + '-error');
if (errorElement) {
errorElement.textContent = message;
errorElement.hidden = false;
}
// Label/Legend finden und markieren
const labelElement =
document.querySelector(`label[for="${fieldId}"]`) ||
document.getElementById(key + '-legend'); // Für fieldsets
if (labelElement) {
labelElement.classList.add(
key === 'contactMethod'
? 'contact-form__legend--error'
: 'contact-form__label--error'
);
// Fehler-Präfix anzeigen
labelElement
.querySelector('.contact-form__error-prefix')
.removeAttribute('hidden');
}
// Input als 'aria-invalid' markieren
const inputElement = document.getElementById(fieldId);
if (inputElement) {
inputElement.setAttribute('aria-invalid', 'true');
}
// Link zur Fehlerzusammenfassung hinzufügen
const li = document.createElement('li');
const a = document.createElement('a');
a.href = `#${fieldId}`; // Link zur Input-ID
const labelText =
labelElement?.dataset.errorsummaryLabel ||
labelElement?.textContent
.replace('*', '')
.replace('Fehler:', '')
.trim() ||
key;
a.textContent = `${labelText}: ${message}`;
li.appendChild(a);
errorSummaryList.appendChild(li);
});
errorSummary.hidden = false;
}
/**
* Setzt alle Fehleranzeigen im Formular zurück.
*/
function resetErrors() {
errorSummary.hidden = true;
errorSummaryList.innerHTML = '';
errorSummaryIntro.textContent = '';
// Alle Inline-Fehler und Attribute zurücksetzen
form.querySelectorAll('.contact-form__error-message').forEach((el) => {
el.hidden = true;
el.textContent = '';
});
form.querySelectorAll('[aria-invalid="true"]').forEach((el) => {
el.setAttribute('aria-invalid', 'false');
});
form
.querySelectorAll(
'.contact-form__label--error, .contact-form__legend--error'
)
.forEach((el) => {
el.classList.remove(
'contact-form__label--error',
'contact-form__legend--error'
);
});
form.querySelectorAll('.contact-form__error-prefix').forEach((el) => {
el.setAttribute('hidden', 'true');
});
}
});
Damit ist die barrierefreie Fehlerbehandlung implementiert!
Absendestatus (Loading) barrierefrei gestalten
Nachdem ein Nutzer auf "Senden" klickt und das Formular valide ist, beginnt der Sendevorgang. Dieser Netzwerk-Request dauert einen Moment. Wenn Du hier kein Feedback gibst, ist der Nutzer unsicher:
- "Ist etwas passiert?"
- "Muss ich nochmal klicken?"
Dieses unsichere "Dazwischen" müssen wir barrierefrei überbrücken. Die beste Methode ist, den Button zu deaktivieren, um doppeltes Absenden zu verhindern [27], und einen "Beschäftigt"-Status zu kommunizieren.
Wir passen dafür den else-Block in unserem submit-Event-Listener an.
// ... im submit-Event-Listener ...
// ... (Variablen-Definitionen)
const submitButton = form.querySelector('button[type="submit"]');
if (Object.keys(newErrors).length > 0) {
// ... (Fehlerbehandlung wie oben) ...
} else {
// KEINE FEHLER: Formular absenden
// 1. UI-Status auf 'submitting' setzen
// Diese Funktion steuert den Button, Spinner und Live-Region
updateUIforStatusChange('submitting');
// 2. Hier kommt Deine echte Sende-Logik (z.B. fetch)
// In diesem Beispiel simulieren wir es:
simulateFormSend() // Diese Funktion gibt ein Promise zurück
.then((response) => {
// Erfolgsfall behandeln
updateUIforStatusChange('success');
})
.catch((error) => {
// Fehlerfall behandeln
updateUIforStatusChange('error');
});
}
// ...
/**
* Simuliert das Senden (dauert 3 Sek.)
*/
function simulateFormSend() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ success: true });
}, 3000);
});
}
Für die Statusansage (statusRegion.textContent = ...) brauchen wir eine Live-Region. Diese kannst Du direkt neben Deiner Fehlerzusammenfassung im HTML platzieren. Wichtig ist role="status" und aria-live="polite", damit die Ansage die aktuelle Screenreader-Ausgabe nicht unterbricht [28].
<div
id="form-status-message"
role="status"
aria-live="polite"
class="visually-hidden"
>
<!-- Hier wird der Status angezeigt -->
</div>
Status nach dem Absenden (Erfolg/Fehler)
Nach dem Ladevorgang gibt es zwei Ergebnisse: Erfolg (HTTP 200) oder Fehler (z.B. Server-Fehler, HTTP 500). Beides musst Du dem Nutzer unmissverständlich mitteilen. Hier gibt es zwei gängige, barrierefreie Szenarien.
- Szenario 1: Weiterleitung auf eine Bestätigungsseite (Empfohlen)
- Szenario 2: Statusanzeige direkt im Formular (ohne Weiterleitung)
Das ist die robusteste und einfachste Methode.
- Bei Erfolg: Leite den Nutzer auf eine "Danke"-Seite (z.B.
/kontakt/danke). - Bei Fehler: Leite den Nutzer auf eine "Fehler"-Seite (z.B.
/kontakt/fehler).
Hier ein Beispiel für die Erfolgs-Weiterleitung im handleSuccess():
function handleSuccess(response) {
// 1. Erfolgs-Weiterleitung
window.location.href = '/kontakt/danke';
}
Damit das barrierefrei ist, muss die neue Seite:
- Einen klaren, beschreibenden
<title>-Tag haben (z.B. "Danke für Ihre Nachricht - Meine Webseite"). - Eine klare
<h1>-Überschrift haben (z.B. "Vielen Dank!"). - Der Fokus wird beim Laden der neuen Seite automatisch an den Anfang des Dokuments gesetzt, wodurch der Screenreader den neuen Seitentitel und die
<h1>vorliest.
Du brauchst hierfür kein aria-live oder Fokusmanagement, da der Browser-Seitenwechsel den Kontextwechsel bereits klar kommuniziert.
Wenn Du keine Weiterleitung möchtest, muss die Statusmeldung aktiv zum Nutzer gebracht werden. Wir blenden das Formular aus und zeigen eine Statusmeldung an, die den Fokus erhält.
Ganz wichtig: Diese Meldung muss einen Button enthalten, um das Formular zurückzusetzen. Sonst steckt der Nutzer in einer Sackgasse.
<div
id="form-final-status"
class="contact-form__final-status"
role="alert"
tabindex="-1"
hidden
>
<div id="final-status-success" hidden>
<h2>Vielen Dank!</h2>
<p>Ihre Nachricht wurde erfolgreich gesendet. (Dies ist eine Demo).</p>
<button type="button" class="contact-form__reset-button">
Erneut ausfüllen
</button>
</div>
<div id="final-status-error" hidden>
<h2>Fehler beim Senden</h2>
<p>Leider ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.</p>
<button type="button" class="contact-form__reset-button">
Erneut versuchen
</button>
</div>
</div>
// --- Diese Elemente müssen global im Script verfügbar sein ---
const form = document.getElementById('contact-form-demo');
const finalStatus = document.getElementById('form-final-status');
const successView = document.getElementById('final-status-success');
const errorView = document.getElementById('final-status-error');
const statusMessage = document.getElementById('form-status-message');
const submitButton = form.querySelector('button[type="submit"]');
const submitButtonText = submitButton.querySelector(
'.contact-form__submit-text'
);
const spinner = submitButton.querySelector('.contact-form__spinner');
const resetButtons = document.querySelectorAll('.contact-form__reset-button');
const fnameInput = document.getElementById('fname-demo'); // Erstes Feld
// Globale Status-Variable
let status = 'idle';
/**
* Aktualisiert die gesamte Formular-UI basierend auf dem Status.
* Diese Funktion wird vom 'submit'-Listener aufgerufen.
*/
function updateUIforStatusChange(newStatus) {
status = newStatus;
// --- Ladezustand (Submitting) ---
const isSubmitting = status === 'submitting';
submitButton.disabled = isSubmitting;
spinner.hidden = !isSubmitting;
submitButtonText.textContent = isSubmitting
? 'Wird gesendet...'
: 'Absenden (Demo)';
statusMessage.textContent = isSubmitting ? 'Formular wird gesendet...' : '';
// --- Finale Zustände (Erfolg oder Server-Fehler) ---
const isSuccess = status === 'success';
const isError = status === 'error';
const isFinal = isSuccess || isError;
// Formular ausblenden, Status-Box anzeigen
form.hidden = isFinal;
finalStatus.hidden = !isFinal;
if (isFinal) {
// Korrekten inneren Block anzeigen (Erfolg vs. Fehler)
successView.hidden = !isSuccess;
errorView.hidden = !isError;
// CSS-Modifier für Styling setzen
finalStatus.classList.toggle(
'contact-form__final-status--success',
isSuccess
);
finalStatus.classList.toggle('contact-form__final-status--error', isError);
// WICHTIG: Fokus auf die Status-Box setzen!
finalStatus.focus();
} else {
// CSS-Modifier entfernen, wenn wir zurücksetzen
finalStatus.classList.remove(
'contact-form__final-status--success',
'contact-form__final-status--error'
);
}
}
/**
* Event-Listener für die Reset-Buttons (in Erfolg/Fehler-Box).
*/
resetButtons.forEach((button) => {
button.addEventListener('click', () => {
handleReset();
});
});
/**
* Setzt das gesamte Formular in den Ausgangszustand zurück.
*/
function handleReset() {
form.reset(); // Native HTML-Formular-Reset-Methode
resetErrors(); // Unsere existierende Fehler-Reset-Funktion
updateUIforStatusChange('idle'); // UI zurücksetzen
// WICHTIG: Fokus auf das erste Feld zurücksetzen!
fnameInput.focus();
}
Durch das Setzen des Fokus auf den role="alert"-Container zwingen wir den Screenreader, die Erfolgs- oder Fehlermeldung sofort vorzulesen. Der handleReset()-Flow stellt sicher, dass der Nutzer (insbesondere Tastatur- und Screenreader-Nutzer) einen klaren Weg zurück zum Anfang hat.
Fazit
Ein barrierefreies Formular zu erstellen, ist kein Hexenwerk, sondern solides Handwerk. Es erfordert Sorgfalt und ein Verständnis dafür, wie unterschiedliche Menschen mit dem Web interagieren.
Indem Du semantisches HTML verwendest (label, fieldset), klare Strukturen schaffst und eine fehlertolerante, unterstützende Benutzerführung implementierst, erfüllst Du nicht nur die gesetzlichen Anforderungen des BFSG [1]. Du baust ein besseres, benutzerfreundlicheres Produkt für alle Deine Nutzer [29]. Die hier gezeigten Techniken sind universell einsetzbar und ein entscheidender Schritt hin zu einem wirklich inklusiven Internet.
Eine zusammengefasste Checkliste aller Punkte aus dieser Anleitung findest Du in meiner Formular-Checkliste für Web-Entwickler.
Häufig gestellte Fragen (FAQ) zu barrierefreien Formularen
Warum novalidate im <form>-Tag verwenden?
Das Attribut novalidate schaltet die Standard-Fehlermeldungen des Browsers ab. Diese sind oft nicht barrierefrei (z.B. nur als kleine Tooltips sichtbar, die nach kurzer Zeit verschwinden) und nicht einheitlich gestaltet [19]. Mit novalidate übernehmen wir die volle Kontrolle und können eine robuste, für alle zugängliche Fehlerbehandlung mit JavaScript implementieren.
Reicht ein roter Rahmen für Fehlermeldungen nicht aus?
Nein. Ein roter Rahmen ist eine rein visuelle Information und für Nutzer mit Sehbehinderungen (z.B. Farbenblindheit) oder bei Nutzung eines Screenreaders nicht wahrnehmbar [23]. Barrierefreie Fehlerbehandlung erfordert programmatische Zustände (wie aria-invalid="true") [22] und Text-Alternativen, die von assistiven Technologien ausgelesen werden können [18].
Muss ich WCAG 2.1 oder 2.2 befolgen?
Das BFSG bezieht sich über die EU-Norm EN 301 549 [30] derzeit auf die WCAG 2.1 [31]. Allerdings ist WCAG 2.2 [32] der neuere, empfohlene Standard. Es ist eine bewährte Praxis (Best Practice), sich bereits an WCAG 2.2 zu orientieren, da dies zukunftssicher ist und die Anforderungen von 2.1 vollständig abdeckt. Mehr dazu findest Du in meinem Bereich "Gesetzeslage rund um digitale Barrierefreiheit".
Was ist der Unterschied zwischen required und aria-required="true"?
required ist ein HTML5-Attribut, das dem Browser mitteilt, dass ein Feld ausgefüllt werden muss (und löst die native Validierung aus, wenn novalidate fehlt). aria-required="true" ist ein ARIA-Attribut, das assistiven Technologien (wie Screenreadern) explizit und auf die robusteste Weise mitteilt, dass ein Feld ein Pflichtfeld ist. Es ist am besten, beide zu verwenden, um eine maximale Kompatibilität und semantische Klarheit zu gewährleisten, weil in manchen Fällen required allein nicht von allen Technologien korrekt interpretiert wird oder ARIA nicht unterstützt wird.
Was ist mit der Klick-Größe von Buttons (Target Size)?
Eine wichtige Neuerung in WCAG 2.2 ist das Kriterium 2.5.8 Target Size (Minimum) (Level AA) [33]. Es besagt, dass interaktive Elemente (Buttons, Links, Radio-Buttons, Checkboxen) eine Mindestgröße von 24x24 CSS-Pixeln nicht unterschreiten sollten.
Dies ist entscheidend für Nutzer mit motorischen Einschränkungen, z.B. auf Touch-Geräten oder bei zittrigen Händen, da es das versehentliche Klicken auf das falsche Element reduziert. Achte bei deinem CSS darauf, dass deine Klick-Ziele (insbesondere die oft zu kleinen Radio-Buttons und Checkboxen) diese Anforderung erfüllen.
Haftungsausschluss
Der Inhalt dieses Leitfadens dient ausschließlich zu Informationszwecken und stellt keine Rechtsberatung dar. Obwohl ich mich bemühe, genaue und aktuelle Informationen bereitzustellen, übernehme ich keine Gewähr für die Vollständigkeit, Richtigkeit oder Aktualität der bereitgestellten Informationen. Die Einhaltung der Barrierefreiheitsstandards kann je nach spezifischem Kontext und Anwendungsfall variieren. Es wird empfohlen, bei der Implementierung von barrierefreien Formularen professionelle Beratung in Anspruch zu nehmen und regelmäßige Tests mit echten Benutzern durchzuführen, um sicherzustellen, dass die Anforderungen der WCAG-2.2-Richtlinien erfüllt werden. Ich hafte nicht für Schäden oder Verluste, die sich aus der Nutzung oder dem Vertrauen auf die in diesem Leitfaden enthaltenen Informationen ergeben.