DEIN TÄGLICHES UI/UX-COACHING / LEKTION 16

Formularvalidierung: Fehler verhindern, ohne Nutzer zu nerven

Ein Fehlerhinweis kann helfen – oder eine Eingabe unterbrechen, die noch gar nicht fertig ist. Heute entscheidest du, wann ein Formular prüfen, wann es warnen und wie es bei der Korrektur unterstützen sollte.

ca. 15–16 Minuten inklusive Übung

Dein Lernziel

Du kombinierst Validierungszeitpunkte, trennst einen ungültigen Wert von einer sichtbaren Fehlermeldung und gestaltest verständliche Korrekturwege mit sinnvoller Fokusführung.

01 / MINI-LEKTION · 3 MINUTEN

Ist die Eingabe falsch oder nur noch nicht fertig?

Du legst ein Kundenprojekt an und beginnst die E-Mail-Adresse mit kunde@. Sofort erscheint „Ungültige E-Mail-Adresse“. Die Eingabe erfüllt zwar noch nicht die gewünschte Form, aber du bist mitten im Tippen. Das Feedback kann in diesem Moment eher stören als helfen.

  • Während der Eingabe: Eine bekannte Grenze oder ein bereits sichtbarer Fehler lässt sich unmittelbar prüfen. Unfertige Zwischenstände können aber unnötige Warnungen auslösen.
  • Nach dem Verlassen: Ein ausgefülltes Feld wird geprüft, wenn die Person zum nächsten wechselt. Das lässt zunächst Raum für die Eingabe, liefert die Rückmeldung aber später.
  • Beim Absenden: Bis zum Abschluss bleibt die Eingabe ungestört. Es können jedoch mehrere Probleme gleichzeitig auftreten, die dann gut auffindbar sein müssen.

Keine Strategie ist immer überlegen. Für diese Übung kombinierst du sie: leere Pflichtfelder erst nach dem Absendeversuch, Formatfehler in ausgefüllten Feldern nach dem Verlassen und die Korrektur bereits gezeigter Fehler direkt bei einer gültigen Eingabe. Das ist eine eigene Gestaltungsentscheidung, kein allgemeines Gesetz.

Eine Prüfung auf bereits vergebene Projektnamen wäre ein anderer Fall: Sie benötigt gegebenenfalls eine Serveranfrage, einen nachvollziehbaren Prüfstatus und Schutz vor verspäteten Antworten. Das lokale Formular unten führt eine solche Verfügbarkeitsprüfung ausdrücklich nicht durch.

02 / DEINE CHALLENGE · 8 MINUTEN

Drei Zustände desselben Projektformulars.

Die Anforderungen: Projektname ist Pflicht und höchstens 80 Zeichen lang. Eine Kunden-E-Mail ist erforderlich. Budget ist optional, muss bei einer Angabe aber mindestens 100 € betragen. Der Fertigstellungstermin ist Pflicht und liegt nach dem heutigen Tag.

Für das fiktive Produkt gilt zusätzlich eine vereinfachte E-Mail-Regel: Die Domain enthält einen Punkt. Deshalb wird kunde@firma hier zurückgewiesen. Das ist keine allgemeine Aussage über die Gültigkeit aller E-Mail-Adressen. Auch ein akzeptiertes Format beweist nicht, dass die Adresse existiert oder erreichbar ist.

  1. A · Erste Eingabe · 2 Minuten: Das Formular wurde gerade geöffnet. Zeige Labels, Pflichtangaben und notwendige Formatregeln, ohne bereits Fehler für leere Felder auszurufen.
  2. B · Fehlgeschlagener Versuch · 3 Minuten: Projektname leer, E-Mail kunde@firma, Budget 50 € und Termin gestern. Nach „Projekt erstellen“ müssen vier konkrete Probleme und der fehlgeschlagene Abschluss erkennbar sein. Entscheide, ob eine Übersicht zusätzlich zu Feldhinweisen hilft.
  3. C · Korrektur · 3 Minuten: Das Budget wird von 50 auf 500 € geändert. Wann verschwindet sein Fehler? Wie bleibt erkennbar, dass drei andere Felder noch zu korrigieren sind? Verhindere unnötige Layoutsprünge, ohne lange Hinweise abzuschneiden.

Für den ersten Entwurf reichen Wireframes in Figma oder ein kleiner React-Prototyp. Notiere zu jedem Zustand auch Fokus, Tastaturbedienung und die Verbindung zwischen Feld und Hinweis.

03 / BEDIENBARE MUSTERLÖSUNG

Korrigieren, ohne die Orientierung zu verlieren.

Beginne mit A und tippe eine unvollständige Adresse. Verlasse das Feld und kehre zurück. Vergleiche anschließend B und C oder korrigiere das Budget selbst. Die Vorschau verwendet das lokale Datum deines Browsers; „gestern“ in B und C wird beim Einsetzen berechnet.

BEDIENBARE MUSTERLÖSUNG · NUR LOKALE DEMO

Die Schalter setzen Beispieldaten ein. Du kannst danach alle Felder selbst bearbeiten. Es wird kein Projekt angelegt; Eingaben werden weder gesendet noch gespeichert. Nutze Beispieldaten.

Neues Kundenprojekt

Alle Felder außer dem Budget sind Pflichtfelder.

Das lokale Prüfdatum wird vorbereitet.

Pflichtfeld · höchstens 80 Zeichen.

Pflichtfeld · Demo-Regel: Domain mit Punkt, etwa kunde@example.com. Keine Prüfung der Erreichbarkeit.

Optional · mindestens 100 €, wenn angegeben. Komma oder Punkt für Cent; keine Tausendertrennzeichen und kein €-Zeichen.

Pflichtfeld · TT.MM.JJJJ · muss nach dem heutigen Tag liegen.

Keine Netzwerkabfrage, keine Verfügbarkeitsprüfung des Projektnamens und keine E-Mail-Zustellung. Der Button prüft ausschließlich diese lokale Eingabe.

04 / TECHNISCHER IMPULS · 3 MINUTEN

Wert prüfen und Fehler zeigen sind zwei Entscheidungen.

Validierungsregeln, Fokusführung und Quellcode öffnen

Das Formular ist ein eigener, fiktiver Gestaltungsvorschlag. Es dokumentiert weder durchgeführte Nutzertests noch ein fertiges Formular-Backend.

1. Regeln bleiben unabhängig vom Anzeigezeitpunkt.

projectErrors prüft die Werte. visibleProjectErrors entscheidet, welche Meldungen angezeigt werden. Vor dem ersten Absendeversuch bleiben leere Pflichtfelder ohne Fehlermeldung. Ein ausgefülltes Feld wird nach dem Verlassen freigegeben. Beim Absenden werden alle Regeln geprüft.

Ist ein Fehler bereits sichtbar, verschwindet er, sobald der Wert tatsächlich gültig ist. Nur wieder in das Feld zu klicken reicht nicht. Die übrigen Fehler und Eingaben bleiben bestehen. Nach einem Absendeversuch werden auch wieder geleerte Pflichtfelder weiterhin als Fehler behandelt.

2. Geldbeträge brauchen eine erklärte Eingabesyntax.

Das Budgetfeld akzeptiert zum Beispiel 500, 500,00 und 500.00. Währungszeichen, Tausendertrennzeichen und mehr als zwei Nachkommastellen werden mit einer konkreten Korrekturhilfe zurückgewiesen. Das ist eine bewusste Vereinfachung für diese Demo; eine produktive Anwendung könnte weitere regionale Schreibweisen ausdrücklich unterstützen.

Der Parser zerlegt Euro und Cent und bildet einen sicheren ganzzahligen Cent-Betrag. Er multipliziert nicht einfach einen eingelesenen Dezimalbetrag mit 100. Ein leerer Wert bedeutet „nicht angegeben“, nicht automatisch null Euro. Die Mindestgrenze wird erst bei einer vorhandenen Angabe angewendet.

3. Ein Datum ist ein Kalendertag, kein willkürlicher Zeitstempel.

Das Feld nimmt TT.MM.JJJJ entgegen. Die Prüfung erkennt auch unmögliche Tage und Schaltjahre. Anschließend werden gültige Kalendertage im ISO-Format mit dem lokalen heutigen Tag verglichen. Heute selbst genügt nicht; morgen ist der früheste erlaubte Tag. Beim Verlassen und Absenden wird das Prüfdatum aktualisiert.

In einem echten Produkt musst du klären, welche Zeitzone für den Auftrag gilt. Browserdatum und Serverdatum können voneinander abweichen. Der Client darf daher keine abschließende fachliche Autorität sein.

4. Die Fehlerübersicht führt zu konkreten Stellen.

Ein fehlgeschlagener Absendeversuch setzt den Fokus auf eine benannte, fokussierbare Zusammenfassung oberhalb der Felder. Ihre Links fokussieren das jeweilige Eingabefeld. Beim normalen Tippen und Korrigieren springt der Fokus nicht. Die Liste zeigt die noch offenen Probleme; sie ist keine ständig unterbrechende Live-Ansage pro Zeichen.

Die lokalen Fehler stehen direkt am Feld und sind zusätzlich textlich als „Fehler“ gekennzeichnet. Hilfetext und Rückmeldung sind gemeinsam über aria-describedby verknüpft. Eine erfolgreiche lokale Prüfung erhält ebenfalls eine klare Bestätigung, ohne einen echten Projektabschluss vorzutäuschen.

5. Platz reservieren, aber Text wachsen lassen.

Die Rückmeldungsfläche besitzt eine Mindesthöhe statt einer festen Höhe. Dadurch bewegt sich die Folgeeingabe beim Entfernen eines kurzen Hinweises weniger. Mehrzeilige Meldungen dürfen trotzdem wachsen. Auf kleinen Ansichten ist mehr Platz reserviert. Prüfe mit deinen tatsächlichen Texten und vergrößerter Schrift, ob das Verhältnis aus Stabilität und Leerraum passt.

6. Clientprüfung ist kein Zugriffsschutz.

noValidate schaltet hier die native Absendeprüfung aus, damit die eigene Übersicht und Fokusführung konsistent demonstriert werden können. Labels, passende Eingabetypen und Pflichtkennzeichnungen bleiben erhalten. Eine echte Anwendung muss Eingaben zusätzlich auf dem Server prüfen, Berechtigungen beachten und Serverfehler ohne Datenverlust darstellen. Die vereinfachte E-Mail-Prüfung der Demo ist keine vollständige RFC-Validierung.

coaching-validation.ts

export const projectFields = ["name", "email", "budget", "deadline"] as const;
export type ProjectField = typeof projectFields[number];
export type ProjectValues = Record<ProjectField, string>;
export type ProjectErrors = Record<ProjectField, string | null>;
export const projectLabels: Record<ProjectField, string> = { name: "Projektname", email: "Kunden-E-Mail", budget: "Budget", deadline: "Fertigstellung" };
export const emptyProject: ProjectValues = { name: "", email: "", budget: "", deadline: "" };
export function budgetCents(value: string): number | null {
  if (!/^\d+(?:[,.]\d{1,2})?$/.test(value.trim())) return null;
  const [whole, fraction = ""] = value.trim().split(/[,.]/);
  const cents = Number(whole) * 100 + Number(fraction.padEnd(2, "0"));
  return Number.isSafeInteger(cents) ? cents : null;
}
export function germanDateISO(value: string): string | null {
  const match = /^(\d{2})\.(\d{2})\.(\d{4})$/.exec(value.trim());
  if (!match) return null;
  const [, day, month, year] = match;
  const d = Number(day), m = Number(month), y = Number(year);
  const leap = y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0);
  const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  return y >= 1 && m >= 1 && m <= 12 && d >= 1 && d <= days[m - 1] ? `${year}-${month}-${day}` : null;
}
export function projectErrors(values: ProjectValues, today: string): ProjectErrors {
  const name = values.name.trim();
  const email = values.email.trim();
  const budget = values.budget.trim();
  const date = germanDateISO(values.deadline);
  // Deliberately limited product rule for this exercise, not full RFC validation.
  const simpleEmail = /^[^\s@]+@[^\s@.]+(?:\.[^\s@.]+)+$/;
  return {
    name: !name ? "Gib einen Projektnamen ein." : Array.from(name).length > 80 ? "Kürze den Projektnamen auf höchstens 80 Zeichen." : null,
    email: !email ? "Gib eine Kunden-E-Mail-Adresse ein." : !simpleEmail.test(email) ? "Nutze für diese Demo eine Adresse mit Domain und Punkt, zum Beispiel kunde@example.com." : null,
    budget: !budget ? null : budgetCents(budget) === null ? "Gib einen Betrag ohne €-Zeichen oder Tausendertrennzeichen ein, zum Beispiel 500,00." : budgetCents(budget)! < 10000 ? "Das Budget muss mindestens 100 € betragen." : null,
    deadline: !values.deadline.trim() ? "Gib einen Fertigstellungstermin ein." : !date ? "Gib ein gültiges Datum als TT.MM.JJJJ ein, zum Beispiel 25.12.2030." : today && date <= today ? "Wähle einen Termin nach dem heutigen Tag." : null,
  };
}
export function visibleProjectErrors(values: ProjectValues, errors: ProjectErrors, revealed: Partial<Record<ProjectField, boolean>>, submitted: boolean): ProjectErrors {
  return Object.fromEntries(projectFields.map(field => [field, errors[field] && (submitted || (revealed[field] && values[field].trim() !== "")) ? errors[field] : null])) as ProjectErrors;
}
export function localDay(now = new Date()): string {
  return `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,"0")}-${String(now.getDate()).padStart(2,"0")}`;
}
export function germanDay(iso: string): string { return iso.split("-").reverse().join("."); }
export function offsetDay(iso: string, days: number): string {
  const value = new Date(`${iso}T12:00:00Z`);
  value.setUTCDate(value.getUTCDate() + days);
  return value.toISOString().slice(0,10);
}

coaching-validation.tsx

"use client";

import { useEffect, useId, useRef, useState, type FormEvent } from "react";
import { emptyProject, germanDay, localDay, offsetDay, projectErrors, projectFields, projectLabels, visibleProjectErrors, type ProjectField, type ProjectValues } from "@/lib/coaching-validation";
import "./coaching-validation.css";

const help: Record<ProjectField, string> = {
  name: "Pflichtfeld · höchstens 80 Zeichen.",
  email: "Pflichtfeld · Demo-Regel: Domain mit Punkt, etwa kunde@example.com. Keine Prüfung der Erreichbarkeit.",
  budget: "Optional · mindestens 100 €, wenn angegeben. Komma oder Punkt für Cent; keine Tausendertrennzeichen und kein €-Zeichen.",
  deadline: "Pflichtfeld · TT.MM.JJJJ · muss nach dem heutigen Tag liegen.",
};
export function CoachingValidation() {
  const id = useId();
  const [values, setValues] = useState<ProjectValues>({ ...emptyProject });
  const [today, setToday] = useState("");
  const [revealed, setRevealed] = useState<Partial<Record<ProjectField, boolean>>>({});
  const [submitted, setSubmitted] = useState(false);
  const [success, setSuccess] = useState(false);
  const [focusRequest, setFocusRequest] = useState({ count: 0, success: false });
  const summary = useRef<HTMLDivElement>(null);
  const confirmation = useRef<HTMLDivElement>(null);
  const inputs = useRef<Partial<Record<ProjectField, HTMLInputElement | null>>>({});
  useEffect(() => { setToday(localDay()); }, []);
  useEffect(() => {
    if (focusRequest.count > 0) (focusRequest.success ? confirmation.current : summary.current)?.focus();
  }, [focusRequest]);
  const errors = projectErrors(values, today);
  const visible = visibleProjectErrors(values, errors, revealed, submitted);
  const failed = projectFields.filter(field => visible[field]);
  function change(field: ProjectField, value: string) {
    setValues(previous => ({ ...previous, [field]: value }));
    setSuccess(false);
  }
  function preset(which: "initial" | "errors" | "corrected") {
    const current = localDay();
    setToday(current);
    setValues(which === "initial" ? { ...emptyProject } : { name: "", email: "kunde@firma", budget: which === "corrected" ? "500" : "50", deadline: germanDay(offsetDay(current,-1)) });
    setRevealed({}); setSubmitted(which !== "initial"); setSuccess(false);
  }
  function submit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const current = localDay();
    setToday(current); setSubmitted(true);
    const next = projectErrors(values, current);
    const accepted = projectFields.every(field => !next[field]);
    setSuccess(accepted);
    setFocusRequest(previous => ({ count: previous.count + 1, success: accepted }));
  }
  return <div className="validation-lab">
    <p className="eyebrow">BEDIENBARE MUSTERLÖSUNG · NUR LOKALE DEMO</p>
    <div className="validation-presets" role="group" aria-label="Übungszustände einsetzen"><button type="button" onClick={() => preset("initial")}>A · Erste Eingabe</button><button type="button" onClick={() => preset("errors")}>B · Vier Fehler</button><button type="button" onClick={() => preset("corrected")}>C · Budget korrigiert</button></div>
    <p>Die Schalter setzen Beispieldaten ein. Du kannst danach alle Felder selbst bearbeiten. Es wird kein Projekt angelegt; Eingaben werden weder gesendet noch gespeichert. Nutze Beispieldaten.</p>
    <form noValidate onSubmit={submit} aria-labelledby={`${id}-title`}>
      <h3 id={`${id}-title`}>Neues Kundenprojekt</h3>
      <p>Alle Felder außer dem Budget sind Pflichtfelder.</p>
      <p className="validation-clock">{today ? `Prüfdatum dieses Browsers: ${germanDay(today)}. „In der Zukunft“ bedeutet ab morgen.` : "Das lokale Prüfdatum wird vorbereitet."}</p>
      {submitted && failed.length > 0 && <div ref={summary} tabIndex={-1} className="validation-summary" role="region" aria-labelledby={`${id}-summary-title`}><h4 id={`${id}-summary-title`}>Nicht abgeschickt: {failed.length} {failed.length === 1 ? "Feld braucht" : "Felder brauchen"} eine Korrektur.</h4><ul>{failed.map(field => <li key={field}><a href={`#${id}-${field}`} onClick={event => { event.preventDefault(); inputs.current[field]?.focus(); }}>{projectLabels[field]}: {visible[field]}</a></li>)}</ul><p>Deine Eingaben bleiben erhalten.</p></div>}
      {success && <div ref={confirmation} tabIndex={-1} className="validation-success" role="region" aria-labelledby={`${id}-success-title`}><h4 id={`${id}-success-title`}>Demo erfolgreich geprüft.</h4><p>Alle vier Felder erfüllen die Regeln dieser Übung. Es wurde nichts übermittelt und kein Projekt erstellt. Eine echte Anwendung muss zusätzlich serverseitig prüfen.</p></div>}
      <div className="validation-fields">{projectFields.map(field => <div className="validation-field" key={field}>
        <label htmlFor={`${id}-${field}`}>{projectLabels[field]}{field === "budget" ? " in € (optional)" : " (Pflichtfeld)"}</label>
        <p className="validation-help" id={`${id}-${field}-help`}>{help[field]}</p>
        <input ref={element => { inputs.current[field] = element; }} id={`${id}-${field}`} name={field} type={field === "email" ? "email" : "text"} inputMode={field === "budget" ? "decimal" : undefined} autoComplete="off" required={field !== "budget"} value={values[field]} placeholder={field === "budget" ? "500,00" : field === "deadline" ? "TT.MM.JJJJ" : undefined} aria-invalid={Boolean(visible[field])} aria-describedby={`${id}-${field}-help ${id}-${field}-feedback`} onChange={event => change(field,event.target.value)} onBlur={() => { setToday(localDay()); setRevealed(previous => values[field].trim() !== "" ? { ...previous, [field]: true } : previous); }} />
        <div className="validation-feedback" id={`${id}-${field}-feedback`}>{visible[field] ? <p className="validation-error"><strong>Fehler:</strong> {visible[field]}</p> : <span className="validation-feedback-spacer" aria-hidden="true">&nbsp;</span>}</div>
      </div>)}</div>
      <button className="validation-submit" type="submit" disabled={!today}>Projekt erstellen · Demo prüfen</button>
      <p className="validation-note">Keine Netzwerkabfrage, keine Verfügbarkeitsprüfung des Projektnamens und keine E-Mail-Zustellung. Der Button prüft ausschließlich diese lokale Eingabe.</p>
    </form>
  </div>;
}

coaching-validation.css

.validation-lesson .lesson-header h1 { overflow-wrap: anywhere; hyphens: auto; }
.validation-lab { margin: 28px 0; padding: 24px; border: 1px solid #d6dce4; border-radius: 16px; background: #f0f2f5; min-width: 0; }
.validation-presets { display: flex; flex-wrap: wrap; gap: 10px; }
.validation-lab button { min-height: 44px; padding: 10px 14px; border: 1px solid #64748b; border-radius: 8px; background: white; color: #172033; font: inherit; font-size: 14px; cursor: pointer; }
.validation-lab > p:not(.eyebrow) { font-size: 14px; }
.validation-lab form { padding: 24px; margin-top: 24px; border: 1px solid #d6dce4; border-radius: 12px; background: white; min-width: 0; }
.validation-lab form > h3 { font-size: 27px; line-height: 1.3; margin: 0 0 12px; }
.validation-lab form > p { font-size: 14px; }
.validation-lab .validation-clock { color: #475569; font-size: 13px; }
.validation-fields { display: grid; gap: 10px; margin: 24px 0; }
.validation-field { min-width: 0; }
.validation-field label { display: block; font-size: 15px; font-weight: 600; }
.validation-field .validation-help { font-size: 13px; color: #475569; margin: 6px 0 10px; line-height: 1.7; }
.validation-field input { display: block; width: 100%; min-width: 0; min-height: 48px; padding: 12px; border: 1px solid #64748b; border-radius: 7px; background: #fff; color: #172033; font: inherit; font-size: 16px; }
.validation-field input[aria-invalid="true"] { border: 2px solid #a61b1b; padding: 11px; }
.validation-lab :is(input,button,a):focus-visible { outline: 3px solid #2558ed; outline-offset: 3px; }
.validation-feedback { min-height: 70px; padding-top: 8px; }
.validation-feedback .validation-error { color: #a61b1b; font-size: 14px; line-height: 1.7; margin: 0; }
.validation-summary, .validation-success { padding: 18px; margin: 24px 0; border: 2px solid #a61b1b; border-radius: 8px; background: #fff7f7; }
.validation-summary h4,.validation-success h4 { font-size: 19px; line-height: 1.5; margin: 0; }
.validation-summary ul { padding-left: 20px; margin: 12px 0; }
.validation-summary li { margin: 10px 0; font-size: 14px; }
.validation-summary a { color: #9b1c1c; text-decoration: underline; text-underline-offset: 3px; overflow-wrap: anywhere; }
.validation-lab .validation-summary p,.validation-lab .validation-success p { font-size: 14px; margin-bottom: 0; }
.validation-summary:focus,.validation-success:focus { outline: 3px solid #2558ed; outline-offset: 4px; }
.validation-success { border-color: #24654e; background: #f2faf6; }
.validation-lab .validation-submit { background: #1746d4; color: white; border-color: #1746d4; }
.validation-lab .validation-submit:disabled { background: #e5e7eb; color: #475569; border-color: #64748b; cursor: default; }
.validation-lab .validation-note { font-size: 13px; color: #475569; }
.validation-field input { scroll-margin-top: 130px; }
@media(max-width:600px) { .validation-lab,.validation-lab form { padding: 16px; } .validation-summary,.validation-success { padding: 14px; } .validation-feedback { min-height: 110px; } }

05 / ACCESSIBILITY

Problem benennen und Korrektur ermöglichen.

aria-invalid="true" gehört an ein Feld, dessen Fehler nach der gewählten Validierung angezeigt wird, nicht pauschal an jedes anfänglich leere Feld. Die W3C-Technik ARIA21 erläutert diesen Einsatz. Die Technik ist ein möglicher Weg zur Umsetzung, keine allein vorgeschriebene Implementierung. W3C: Using aria-invalid.

WCAG 3.3.1 verlangt bei automatisch erkannten Eingabefehlern die Identifikation des betroffenen Elements und eine Beschreibung in Text. WCAG 3.3.3 verlangt bekannte Korrekturvorschläge, soweit dadurch Sicherheit oder Zweck nicht gefährdet werden. „Ungültig“ ist daher weniger hilfreich als eine konkrete Anforderung wie „Das Budget muss mindestens 100 € betragen“. W3C: Error Identification, W3C: Error Suggestion.

Eine Übersicht ergänzt die lokalen Hinweise, statt sie zu ersetzen. Sie kann besonders bei langen Formularen die Orientierung unterstützen. Das WAI-Formulartutorial zeigt Rückmeldungen am Feld und übergreifende Fehlermitteilungen. W3C WAI: User Notification.

Prüfe mit Tastatur und Screenreader, ob die Übersicht nach dem Absenden auffindbar ist und ihre Links zum passenden Feld führen. Teste außerdem lange Meldungen, die mobile Bildschirmtastatur, 320 CSS-Pixel Breite und vergrößerte Schrift. Ein roter Rahmen allein ersetzt weder die Beschreibung noch eine verständliche Korrekturhilfe.

06 / REFLEXION · 2 MINUTEN

Was braucht die Person als Nächstes?

Eine fehlerhafte E-Mail wird gerade korrigiert.

Verschwindet der Hinweis beim erneuten Fokussieren, beim Erreichen eines gültigen Werts oder erst nach erneutem Verlassen? Begründe deine Wahl.

Mögliche Antwort zum Korrekturzeitpunkt

Für diese Musterlösung wähle ich: sobald der Wert die erklärte Regel erfüllt. Ein bloßer Klick beseitigt das Problem nicht. Einen bereits korrigierten Wert weiter als fehlerhaft darzustellen, könnte dagegen falsches Feedback geben.

Ich verhindere neue Unterbrechungen, indem ich die erstmalige Meldung nicht bei jedem Zeichen zeige und den Fokus während der Korrektur nicht bewege. Bei anderen Regeln, etwa einer noch laufenden Serverprüfung, wäre „gültig“ erst nach dem entsprechenden Ergebnis begründet.

Zwölf Felder, sechs Fehler nach dem Absenden.

Zeigst du nur lokale Hinweise, zusätzlich eine Übersicht oder behandelst du die Fehler nacheinander? Was hilft auf Mobile und mit Screenreader?

Mögliche Antwort zur Fehlerübersicht

Ich würde die sechs Fehler an den Feldern zeigen und durch eine fokussierbare Übersicht mit direkten Sprungzielen ergänzen. Die Person erkennt, dass nichts abgeschickt wurde und wie viele Korrekturen anstehen. Sie kann den Bearbeitungsweg selbst wählen, ohne dass nach jeder Eingabe ein anderes Feld aufspringt.

Auf Mobile dürfen lange Listen und Texte umbrechen. Der Fokus nach dem Absenden und die Links müssen im tatsächlichen Browser funktionieren. Eine schrittweise Formularstruktur kann für die Gesamtaufgabe sinnvoll sein; Fehler nachträglich einzeln zu verstecken wäre dagegen kein Ersatz für einen verständlichen Überblick.

Dein Selbstcheck

  • Zeigst du Anforderungen, bevor Fehler entstehen?
  • Unterscheidest du einen unfertigen Zwischenstand von einer nötigen Korrektur?
  • Sagen Meldungen, welches Feld betroffen ist und was zu tun ist?
  • Verschwindet ein korrigierter Fehler ohne Verlust anderer Eingaben?
  • Bleibt der Fokus beim Tippen stabil und führt nach dem Absenden sinnvoll weiter?

DEIN MERKSATZ

Gute Validierung unterstützt den nächsten Schritt.

Sie bestraft keine unvollständigen Eingaben. Zeitpunkt, Formulierung und Position eines Hinweises sind genauso wichtig wie seine Farbe.