01 / HINSEHEN
Was sagt das Formular, wenn etwas nicht passt?
Wir knüpfen an das Aufgabenformular aus Lektion 2 an. Vergleiche diese beiden fiktiven UI-Skizzen: Wo erkennst du, was du eingeben sollst, warum ein Datum problematisch ist und was beim Absenden passiert?
Neue Aufgabe
Aufgabentitel
Priorität
Neue Aufgabe
Aufgabentitel (Pflichtfeld)
Fälligkeitsdatum
Datum liegt nach Projektende.
Aufgabe erstellenStatische Gestaltungsbeispiele, keine bedienbaren Formulare. A zeigt das problematische Datum nur durch eine rote Umrandung; B ergänzt die Beschriftung und eine Fehlermeldung.
B erklärt mehr über das Systemverhalten. Die Aktion nennt das Ergebnis konkreter, das Datum hat ein eigenes Label und der Fehler wird in Text beschrieben. Trotzdem bleiben Fragen offen: Wie sieht Tastaturfokus aus? Ist die Meldung dem Feld technisch zugeordnet? Und darf die Aufgabe mit diesem Datum angelegt werden?
02 / STATE DESIGN
Ein Feld. Mehrere Aufgaben.
Ein gutes Zustandssystem trennt drei Dinge: den fachlichen Zustand, seine visuelle Darstellung und seine Bedeutung für assistive Technologien. Ein roter Rahmen allein setzt zum Beispiel noch keinen semantischen Fehlerzustand.
Projektende: 24.10.2026. Vorgegebene Werte für diese Zustandsvorschau.
Die Werte dieser Vorschau sind schreibgeschützt. „Fokus“ setzt den tatsächlichen Tastaturfokus ins Feld. Im Fehlerzustand kannst du das Feld ebenfalls per Tab fokussieren: rote Grenze und blauer Fokusindikator bleiben nebeneinander sichtbar. Ein frei bedienbares Formular folgt in der Musterlösung.
Fokus ist kein Gegenstück zu Fehler: Ein Feld kann gleichzeitig fokussiert und ungültig sein. Unsere Gestaltung verwendet deshalb eine rote Feldgrenze für den Fehler und einen separaten blauen Außenring für den Fokus.
03 / DEINE CHALLENGE · 8 MINUTEN
Gestalte ein vollständiges Datumsfeld.
Baue das Feld in Figma oder React. Das fiktive Projekt endet am 24.10.2026; als problematische Eingabe verwenden wir den 31.10.2026.
- Normal: Eindeutiges Label, erkennbares Feld und ein Hilfetext, falls er die Eingabe erleichtert.
- Fokus: Ein gut sichtbarer Indikator bei Tastaturbedienung, der den Fehlerzustand nicht verdeckt.
- Fehler: Eine konkrete Meldung am zugehörigen Feld, mit verständlichem Weg zur Korrektur. Farbe ist nicht der einzige Hinweis.
- Deaktiviert: Das Feld ist tatsächlich nicht bedienbar. Die Begründung steht außerhalb des Feldes und bleibt lesbar.
Entscheide zuerst die Fachregel.
Ist das Projektende eine verbindliche Grenze? Dann muss das Formular eine spätere Fälligkeit abweisen. Ist es nur ein Planungsdatum, kann ein Hinweis genügen und die Eingabe zulässig bleiben. Begründe deine Wahl, bevor du den Rahmen einfärbst.
Ergänze auch den erfolgreichen Abschluss: Was erfährt die Person nach dem Absenden? Das Ausbleiben eines Fehlers bedeutet noch nicht, dass eine echte Aufgabe gespeichert wurde.
04 / VERGLEICHEN UND BEGRÜNDEN
Eine mögliche Musterlösung.
Probiere erst deinen eigenen Entwurf. Diese Lösung zeigt zwei fachlich unterschiedliche Regeln für dasselbe Datum; keine davon ist ohne Nutzungskontext die einzig richtige.
Bedienbare Musterlösung öffnen
Das Datum ist optional. Bei der harten Grenze wird der 31.10.2026 beim Verlassen des Feldes oder beim Absenden als ungültig markiert. Im Warnmodus bleibt er zulässig. Die Kontrolloption „Archiviertes Projekt“ demonstriert den deaktivierten Zustand.
Fachregel: Aufgaben dürfen nicht nach dem Projektende fällig sein.
Lokales Lernbeispiel: Es werden keine Aufgaben gespeichert oder versendet. Die Erfolgsmeldung simuliert ausschließlich das Ergebnis der Eingabeprüfung.
Warum diese Entscheidungen?
- Label und Hilfe bleiben bestehen. Der Platzhalter übernimmt nicht die Beschriftung.
aria-describedbyverknüpft Hilfe und die jeweils relevante Rückmeldung mit dem Feld. - Fehler erst nach Interaktion. Vor dem ersten Verlassen oder Absenden bleibt die Meldung zurückhaltend. Nach einer Korrektur wird ein bestehender Fehler sofort aktualisiert.
- Warnung ist kein ungültiger Wert. Nur ein blockierender Fehler setzt
aria-invalid. Ein zulässiges Datum nach Projektende erhält im Warnmodus einen Hinweis. - Fokus und Fehler haben getrennte Aufgaben. Der Außenring zeigt den Bedienfokus, die innere Feldgrenze und der Text den Fehler. Beide bleiben gleichzeitig sichtbar.
- Deaktivierung wird erklärt. Natives
disabledverhindert die Eingabe und nimmt das Feld aus der Tab-Reihenfolge. Die sichtbare Begründung bleibt unabhängig davon erreichbar. - Absenden hat ein Ergebnis. Ein Fehler wird in Text gemeldet und das Feld fokussiert. Ein gültiger Versuch erhält eine Erfolgsmeldung über eine Statusregion. Dieses Lernbeispiel simuliert den Abschluss; ein echtes Produkt müsste den erfolgreichen Speichervorgang abwarten.
Mit Screenreader und Tastatur solltest du prüfen, ob Label, Hilfe, Fehler und Ergebnis in deinem Zielbrowser verständlich ankommen. Die vorhandenen Attribute allein ersetzen diese Prüfung nicht.
React-Code und CSS der Musterlösung ansehen
Dies ist der Code des oben eingebundenen Beispiels. Die CSS-Datei wird neben der React-Datei gespeichert und von ihr importiert.
coaching-date-field-solution.tsx
"use client";
import { useEffect, useRef, useState, type FormEvent } from "react";
import "./coaching-date-field-solution.css";
export function CoachingDateFieldSolution() {
const [date, setDate] = useState("2026-10-31");
const [rule, setRule] = useState<"limit" | "warning">("limit");
const [disabled, setDisabled] = useState(false);
const [touched, setTouched] = useState(false);
const [submitted, setSubmitted] = useState(false);
const [result, setResult] = useState("");
const [badInput, setBadInput] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const projectEnd = "2026-10-24";
// The date value is ISO (YYYY-MM-DD), or empty. Also check native badInput.
const exceedsEnd = Boolean(date && date > projectEnd);
const showFeedback = !disabled && (touched || submitted);
const invalid = showFeedback && (badInput || (exceedsEnd && rule === "limit"));
const warning = showFeedback && !badInput && exceedsEnd && rule === "warning";
useEffect(() => {
if (submitted && invalid) inputRef.current?.focus();
}, [submitted, invalid]);
function resetFeedback() {
setTouched(false);
setSubmitted(false);
setResult("");
}
function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (disabled) return;
// Read the current native value, including an unfinished or cleared date.
const currentDate = inputRef.current?.value ?? date;
const malformed = Boolean(inputRef.current?.validity.badInput);
const exceedsCurrentEnd = Boolean(currentDate && currentDate > projectEnd);
setDate(currentDate);
setBadInput(malformed);
setSubmitted(true);
setResult("");
if (malformed || (exceedsCurrentEnd && rule === "limit")) return;
setResult(currentDate
? `Simulation erfolgreich: Aufgabe mit Fälligkeitsdatum ${currentDate} erstellt${exceedsCurrentEnd ? " – der Hinweis zum Projektende bleibt bestehen" : ""}.`
: "Simulation erfolgreich: Aufgabe ohne Fälligkeitsdatum erstellt.");
}
return <div className="due-solution">
<fieldset className="due-controls">
<legend>Fachregel für diese Musterlösung</legend>
<label><input type="radio" name="date-rule" checked={rule === "limit"} onChange={() => { setRule("limit"); resetFeedback(); }} /> Harte Grenze</label>
<label><input type="radio" name="date-rule" checked={rule === "warning"} onChange={() => { setRule("warning"); resetFeedback(); }} /> Nur warnen</label>
</fieldset>
<label className="due-disable"><input type="checkbox" checked={disabled} onChange={event => { setDisabled(event.target.checked); resetFeedback(); }} /> Archiviertes Projekt simulieren</label>
<p className="due-rule-note">{rule === "limit"
? "Fachregel: Aufgaben dürfen nicht nach dem Projektende fällig sein."
: "Fachregel: Aufgaben dürfen nach dem Projektende fällig sein; ein Hinweis macht darauf aufmerksam."}</p>
<form noValidate onSubmit={submit}>
<label className="due-label" htmlFor="solution-due-date">Fälligkeitsdatum <span>(optional)</span></label>
<input ref={inputRef} className="due-input" id="solution-due-date" type="date" value={date} disabled={disabled}
aria-invalid={invalid || undefined}
aria-describedby={`solution-date-help${disabled ? " solution-date-disabled" : invalid ? " solution-date-error" : warning ? " solution-date-warning" : ""}`}
onBlur={event => { setDate(event.target.value); setTouched(true); setBadInput(event.target.validity.badInput); }}
onChange={event => { setDate(event.target.value); setBadInput(event.target.validity.badInput); setResult(""); }} />
<p className="due-help" id="solution-date-help">Projektende: 24.10.2026. Du kannst das Datum leer lassen.</p>
{disabled && <p className="due-disabled-reason" id="solution-date-disabled">Das Projekt ist archiviert. Neue Aufgaben und Datumsänderungen sind deshalb nicht verfügbar.</p>}
{invalid && <p className="due-error" id="solution-date-error" role="alert"><strong>Fälligkeitsdatum ungültig:</strong> {badInput ? "Gib ein vollständiges, gültiges Datum ein oder lasse das Feld leer." : "Wähle den 24.10.2026 oder ein früheres Datum, oder lasse das Feld leer."}</p>}
{warning && <p className="due-warning" id="solution-date-warning" role="status"><strong>Hinweis:</strong> Das Datum liegt nach dem Projektende. Du darfst die Aufgabe trotzdem erstellen.</p>}
<div className="due-actions"><button className="due-submit" type="submit" disabled={disabled}>Aufgabe erstellen</button><button className="due-reset" type="button" onClick={() => { setDate("2026-10-31"); setRule("limit"); setDisabled(false); resetFeedback(); }}>Zurücksetzen</button></div>
<div className="due-result" role="status" aria-live="polite">{result && <p>{result}</p>}</div>
</form>
<p className="due-demo-note">Lokales Lernbeispiel: Es werden keine Aufgaben gespeichert oder versendet. Die Erfolgsmeldung simuliert ausschließlich das Ergebnis der Eingabeprüfung.</p>
</div>;
}
coaching-date-field-solution.css
.due-solution{max-width:700px;border:1px solid #d5dbe5;border-radius:12px;padding:24px;background:#fff;color:#1c2633}
.due-controls{display:flex;flex-wrap:wrap;gap:12px 24px;border:0;padding:0;margin:0}
.due-controls legend{font-size:15px;font-weight:600;line-height:1.7;margin-bottom:12px}
.due-controls label,.due-disable{display:flex;align-items:center;gap:10px;font-size:14px;line-height:1.8;min-height:44px}
.due-controls input,.due-disable input{width:18px;height:18px;accent-color:#2558ed;flex-shrink:0}
.due-disable{margin-top:10px}
.due-solution p.due-rule-note{font-size:13px;line-height:1.8;color:#505d70;margin:12px 0 24px;padding-bottom:20px;border-bottom:1px solid #d5dbe5}
.due-label{display:block;font-size:14px;font-weight:600;margin-bottom:10px;line-height:1.7}
.due-label span{font-weight:400}
.due-input{display:block;width:100%;min-width:0;max-width:380px;min-height:48px;border:2px solid #737e90;border-radius:6px;padding:10px 12px;background:#fff;color:#1c2633;font:16px/1.5 sans-serif}
.due-input[aria-invalid=true]{border-color:#a32626}
.due-input:focus{outline:3px solid #2558ed;outline-offset:3px}
.due-input:disabled{color:#5e6775;background:#eef0f3;border-color:#b6bec9;cursor:not-allowed;opacity:1}
.due-solution p.due-help,.due-solution p.due-disabled-reason{font-size:13px;line-height:1.8;color:#505d70;margin-top:12px}
.due-solution p.due-error{font-size:14px;line-height:1.8;color:#a32626;margin-top:12px}
.due-solution p.due-warning{font-size:14px;line-height:1.8;color:#79500d;background:#fff7e4;padding:12px;border-left:3px solid #96631b;margin-top:12px}
.due-actions{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-top:22px}
.due-submit,.due-reset{min-height:46px;padding:10px 16px;font-size:14px;line-height:1.7;border-radius:6px}
.due-submit{background:#2558ed;color:white;border:1px solid #2558ed}
.due-submit:hover:not(:disabled){background:#1743cb}
.due-submit:disabled{color:#5e6775;background:#e4e8ef;border-color:#bdc6d4;cursor:not-allowed}
.due-reset{background:white;border:1px solid #737e90;color:#253147}
.due-solution :is(button,input):focus-visible{outline:3px solid #2558ed;outline-offset:3px}
.due-result:not(:empty){border-left:3px solid #286744;background:#eaf4ed;padding:16px;margin-top:20px}
.due-result p{font-size:14px;line-height:1.8;color:#245138;margin:0}
.due-solution p.due-demo-note{font-size:12px;line-height:1.8;color:#505d70;margin-top:20px}
@media(max-width:640px){.due-solution{padding:20px}.due-actions{align-items:stretch}.due-controls{gap:0 16px}.due-controls label,.due-disable{font-size:13px}.due-input{width:100%}}
05 / ACCESSIBILITY IN DER PRAXIS
Vier Anforderungen für deinen Check.
| Was du prüfst | Anforderung und Quelle |
|---|---|
| Normaler Text | Mindestens 4,5:1 Kontrast; für großen Text gilt 3:1. Ausnahmen gelten unter anderem für Text in inaktiven Bedienelementen. 1.4.3 Contrast (Minimum), AA |
| Wesentliche visuelle Hinweise | Zum Erkennen von Bedienelementen und Zuständen notwendige visuelle Informationen benötigen mindestens 3:1 zu angrenzenden Farben. Inaktive und unveränderte native Komponenten sind ausgenommen. Nicht jede dekorative Linie muss diesen Wert erfüllen. 1.4.11 Non-text Contrast, AA |
| Erkannte Eingabefehler | Das betroffene Feld identifizieren und den Fehler in Text beschreiben. Eine konkrete Korrekturhilfe ergänzt die Fehlermeldung sinnvoll. 3.3.1 Error Identification, A |
| Tastaturfokus | Es muss einen Bedienmodus geben, in dem der Tastaturfokus sichtbar ist. 2.4.7 Focus Visible, AA |
Ein aria-invalid-Attribut beschreibt einen Fehlerzustand, kündigt aber nicht automatisch jede neue Meldung an. In der Musterlösung wird eine neu eingeblendete Fehlermeldung zusätzlich über role="alert" angekündigt; Hinweise und Erfolge nutzen Statusregionen.
06 / REFLEXION · 2 MINUTEN
Wann hilft die Rückmeldung?
- Wann sollte ein Feld einen Fehler anzeigen: während der Eingabe, beim Verlassen oder erst beim Absenden? Welche Nachteile hat jede Variante?
- Wie gestaltest du ein fokussiertes und gleichzeitig ungültiges Feld, wenn dein Designsystem überall einen blauen Fokusindikator nutzt?
Mögliche Antworten zur Reflexion ansehen
Es kommt auf den Eingabemoment an.
Eine sofortige Fehlermeldung kann während einer noch unvollständigen Eingabe stören. Eine Prüfung beim Verlassen lässt Zeit zum Eingeben, kann aber spät auffallen. Erst beim Absenden wird der ganze Ablauf geprüft, dafür kommen Probleme unter Umständen gesammelt am Ende. Unsere Lösung prüft beim ersten Verlassen oder Absenden und aktualisiert bekannte Fehler beim Korrigieren. Das ist ein bewusster Vorschlag für dieses Beispiel, keine allgemeine WCAG-Vorgabe zum Zeitpunkt.
Zwei Signale gleichzeitig.
Ein blauer Außenring kennzeichnet den Fokus; eine rote Grenze, ein konkreter Fehlertext und die semantische Zuordnung kennzeichnen den Fehler. Dadurch kann die Person erkennen, wo sie gerade arbeitet und was sie ändern muss.
DEIN MERKSATZ
Ein hochwertiges Interface bleibt auch verständlich, wenn etwas schiefgeht.
Gestalte nicht nur den Idealzustand. Erkläre, was gerade gilt, was geändert werden muss und was als Nächstes möglich ist.