Aufbau einer React-Komponente
Meine Vorstellung davon, wie jede React-Komponente aussehen sollte
421
8 min.
Der SSCF-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Kontaktformularen fest.
form verwendet. Es stellt Formularsemantik, Tastaturverhalten und einen browserseitigen Übermittlungsmechanismus bereit.fieldset und legend gruppiert. fieldset wird nur verwendet, wenn die Felder eine sinnvolle logische Gruppe bilden; jedes einzelne Feld muss nicht separat umschlossen werden.legend benennt die Gruppe und steht als erstes Kindelement in fieldset.POST werden method="post" explizit an form und die Adresse des Handlers über action festgelegt. Bei Formularen mit Dateien wird zusätzlich enctype="multipart/form-data" gesetzt.id und einen aussagekräftigen name. id verknüpft das Feld mit seiner Beschriftung; name bezeichnet das Feld im übermittelten Datensatz.input wird ein passender type angegeben, zum Beispiel text, email, tel, checkbox, file, number oder range. select und textarea besitzen kein type-Attribut; die Interaktion wird durch das jeweilige Element bestimmt.required wird nur für Felder verwendet, die im jeweiligen Ablauf tatsächlich erforderlich sind. Optionale Felder erhalten das Attribut nicht.autocomplete wird mit passenden Tokens angegeben, zum Beispiel name, email, tel, given-name oder family-name. Dadurch kann der Browser gespeicherte Daten vorschlagen und den Zweck des Feldes erkennen.placeholder ist optional. Er kann ein kurzes Formatbeispiel zeigen, ersetzt aber weder eine dauerhaft sichtbare label noch die einzige Erklärung eines Feldes.label wird über übereinstimmende Werte von for und id mit dem Feld verknüpft oder umschließt das Feld. Durch Aktivieren der Beschriftung erhält das zugehörige Steuerelement den Fokus.Beispiel für eine semantische Formularstruktur:
<form action="/de/contact" method="post" enctype="multipart/form-data" data-contact-form>
<fieldset>
<legend>Kontaktdaten</legend>
<div class="form-field">
<label for="name">Name</label>
<input
id="name"
name="name"
type="text"
autocomplete="name"
placeholder="Zum Beispiel Anna"
required
/>
</div>
<div class="form-field">
<label for="email">E-Mail-Adresse</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
placeholder="name@example.com"
required
/>
</div>
<div class="form-field">
<label for="message">Nachricht</label>
<textarea
id="message"
name="message"
placeholder="Die Frage kurz beschreiben"
required
></textarea>
</div>
</fieldset>
<div class="form-consent">
<label class="form-consent__label">
<input id="data-consent" name="dataConsent" type="checkbox" required />
<span>Einwilligung zur Datenverarbeitung</span>
</label>
<a href="/de/policy" target="_blank" rel="noopener noreferrer">
Bedingungen zur Datenverarbeitung (öffnet in einem neuen Tab)
</a>
</div>
<button class="form-submit" type="submit">Absenden</button>
<p role="status" aria-live="polite" data-form-status></p>
</form>Das Kontrollkästchen und sein Beschriftungstext sind gemeinsam in label eingeschlossen. Dadurch schaltet auch ein Klick auf den Text das Feld um. Das Element a steht bewusst außerhalb des Labels und bleibt ein separates interaktives Element. Eine interne Seite sollte nur bei begründetem Bedarf in einem neuen Tab geöffnet werden; dieses Verhalten muss kenntlich sein. Bei target="_blank" wird rel="noopener noreferrer" gesetzt.
aria-invalid="true".aria-describedby dem entsprechenden Feld zugeordnet. Sie benennt das Problem und die Korrekturmöglichkeit, zum Beispiel das erforderliche E-Mail-Format.aria-invalid="true" wird erst gesetzt, wenn ein Fehler tatsächlich festgestellt wurde. Nach Korrektur des Werts wird der Zustand aktualisiert oder entfernt.Beispiel für ein Feld mit Fehlermeldung:
<div class="form-field form-field--error">
<label for="email">E-Mail-Adresse</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
aria-invalid="true"
aria-describedby="email-error"
required
/>
<p id="email-error">Eine E-Mail-Adresse in einem gültigen Format eingeben.</p>
</div>required, type="email", min, max und pattern werden verwendet, wenn sie zum jeweiligen Ablauf passen. Eigene Validierung ergänzt nur tatsächlich benötigte Regeln.300 ms eignet sich für aufwendige oder netzwerkbasierte Prüfungen nach einer Eingabepause; er ersetzt weder die Prüfung bei Fokusverlust noch die Prüfung vor dem Absenden.disabled ersetzt keine Fehlermeldung.16px gesetzt, um automatisches Zoomen der Seite beim Fokussieren zu vermeiden.autocomplete-Werte wie name, email und tel angegeben.:autofill und :-webkit-autofill kontrastreiche Text- und Hintergrundfarben festgelegt. Systemstile dürfen nicht auf Kosten lesbarer Inhalte oder des Fokusindikators verborgen werden.Grundlegende Feld- und Autofill-Stile:
.form-field input,
.form-field select,
.form-field textarea,
.phone-field input {
font-size: 16px;
}
.form-field input:focus-visible,
.form-field select:focus-visible,
.form-field textarea:focus-visible,
.phone-field input:focus-visible {
outline: 2px solid #2457d6;
outline-offset: 2px;
}
.form-field [aria-invalid="true"] {
border-color: #b42318;
}
.form-field input:autofill,
.form-field input:-webkit-autofill,
.phone-field input:autofill,
.phone-field input:-webkit-autofill {
-webkit-text-fill-color: #161616;
box-shadow: 0 0 0 1000px #fff inset;
}Die Farben im Beispiel werden durch geprüfte Design-Tokens ersetzt. 16px bezieht sich auf den Text im Eingabefeld; die Größe der Beschriftung oder des Platzhalters ändert nicht die Schriftgröße des Feldtexts.
type="tel" mit autocomplete="tel" verwendet. inputmode="tel" kann mobilen Geräten zusätzlich eine passende Bildschirmtastatur vorschlagen.+ und Ziffern der Landesvorwahl werden nicht allgemein für die manuelle Eingabe gesperrt: Internationale Nummern können eingetippt oder eingefügt werden. Ist die Vorwahl des ausgewählten Landes fest, wird eine doppelte Vorwahl normalisiert statt angehängt.autofocus beim Laden des Formulars. Für Russland sind die nationale Vorwahl 8 und die internationale Vorwahl +7 keine austauschbaren Masken; die Eingabe wird in das ausgewählte Format normalisiert.Im React-Projekt wird für internationale Telefonnummern react-international-phone anstelle von @react-input/mask verwendet. Das Beispiel useMask({ mask, replacement }) gehört zu @react-input/mask und ist nicht die API der Telefonbibliothek. Dafür kommt PhoneInput zum Einsatz; die Komponente formatiert Nummern, unterstützt die Länderauswahl und verwaltet die Cursorposition. Eine IP-basierte Ländererkennung ist keine integrierte Funktion der Bibliothek und ersetzt weder Nummernvalidierung noch die Auswahl durch die Nutzenden.
import { useState } from 'react';
import { PhoneInput } from 'react-international-phone';
import 'react-international-phone/style.css';
export const PhoneField = () => {
const [phone, setPhone] = useState('');
return (
<div className="phone-field">
<label htmlFor="phone">Telefon</label>
<PhoneInput
defaultCountry="ru"
forceDialCode
value={phone}
onChange={(phone) => setPhone(phone)}
inputProps={{
id: 'phone',
name: 'phone',
type: 'tel',
autoComplete: 'tel',
inputMode: 'tel',
required: true,
}}
/>
</div>
);
};react-international-phone übernimmt Eingabe und Formatierung, wird in der aktuellen Dokumentation jedoch nicht mehr als Bibliothek zur Telefonnummernvalidierung geführt. Für die Prüfung anhand von Nummerierungsplänen wird eine spezialisierte Bibliothek wie google-libphonenumber sowie eine serverseitige Prüfung verwendet.
import { PhoneNumberUtil } from 'google-libphonenumber';
const phoneUtil = PhoneNumberUtil.getInstance();
const isValidPhone = (phone) => {
try {
return phoneUtil.isValidNumber(phoneUtil.parseAndKeepRawInput(phone));
} catch (error) {
return false;
}
};Die Prüfung von Format und Nummerierungsplan bestätigt nicht, dass eine Nummer aktiv ist oder der absendenden Person gehört.
<select> verwendet. Es bietet integrierte Tastatur- und Plattformunterstützung.<select> eine tatsächliche Anforderung nicht erfüllt. Zugänglicher Name, geöffneter und ausgewählter Zustand, Tastaturbedienung und vorhersehbares Fokusmanagement werden nach dem passenden ARIA-Muster umgesetzt.<textarea> bleibt möglich, sofern sie das Layout nicht beeinträchtigt. Muss die vertikale Größenänderung begrenzt werden, eignen sich resize: vertical oder eine andere begründete Begrenzung; ein pauschales resize: none kann die Eingabe längerer Texte erschweren.accept schlägt lediglich Dateien im Dateiauswahldialog vor und ist keine Sicherheitsprüfung.<input type="range"> verwendet, sofern dessen Verhalten zur Aufgabe passt. Semantik, Fokus, Tastaturbedienung und Zeigereingabe sind bereits integriert.type="number" ergänzt werden. Bereich, Schrittweite und aktueller Wert werden aus einer gemeinsamen Zustandsquelle synchronisiert.min, max, step und das anfängliche value. Der aktuelle Wert wird zusätzlich als Text angezeigt, wenn die Position allein nicht verständlich genug ist.cursor: pointer kann die Interaktivität anzeigen. cursor: grab und cursor: grabbing werden nur eingesetzt, wenn die Komponente tatsächlich Ziehen unterstützt; der Cursor ersetzt weder Touch- noch Tastaturbedienung.Beispiel für ein gekoppeltes Zahlenfeld und einen Schieberegler:
<div class="form-field">
<label for="budget-range">Budget</label>
<input
id="budget-range"
type="range"
min="0"
max="100000"
step="5000"
value="50000"
aria-describedby="budget-current-value"
/>
<output id="budget-current-value" for="budget-range">50.000 EUR</output>
<label for="budget-value">Genauer Betrag, EUR</label>
<input
id="budget-value"
name="budget"
type="number"
min="0"
max="100000"
step="5000"
value="50000"
inputmode="numeric"
required
/>
</div>Beide Steuerelemente aktualisieren denselben Wert im Interface. In diesem Beispiel besitzt nur das Zahlenfeld ein name, sodass der Wert einmal übermittelt wird.
Beispiel für die Synchronisierung nativer Felder:
const rangeInput = document.querySelector('#budget-range');
const numberInput = document.querySelector('#budget-value');
const budgetOutput = document.querySelector('#budget-current-value');
const formatBudget = (value) => new Intl.NumberFormat('de-DE').format(Number(value)) + ' EUR';
const updateBudgetOutput = () => {
budgetOutput.value = formatBudget(rangeInput.value);
};
rangeInput.addEventListener('input', () => {
numberInput.value = rangeInput.value;
updateBudgetOutput();
});
numberInput.addEventListener('change', () => {
if (!numberInput.validity.valid) return;
rangeInput.value = numberInput.value;
numberInput.value = rangeInput.value;
updateBudgetOutput();
});Der Schieberegler aktualisiert das Zahlenfeld während der Bewegung. Eine eingegebene Zahl wird nach Abschluss der Eingabe und nur dann mit dem Schieberegler synchronisiert, wenn sie die Beschränkungen min, max und step erfüllt.
input[type="range"] {
cursor: pointer;
}
input[type="range"]:active {
cursor: grabbing;
}Tab und Shift+Tab erreichbar; der sichtbare Fokusindikator wird nicht entfernt.<select> folgt den Tastaturkonventionen des Betriebssystems und Browsers. Eine eigene Liste muss ein konsistentes Tastaturmodell einschließlich Navigation und Auswahl implementieren.Leertaste umgeschaltet. Das native Element vermittelt seinen Zustand bereits und benötigt keine doppelten ARIA-Attribute.<label>. aria-label oder aria-labelledby werden nur verwendet, wenn keine sichtbare Beschriftung vorhanden oder diese nicht ausreichend ist.aria-label oder aria-labelledby einen verständlichen Namen.<label> eingeschlossen werden, sodass der gesamte Text das Feld umschaltet. Ein Richtlinienlink steht außerhalb des Labels, damit kein interaktiver Link darin verschachtelt wird.role="status" und aria-live="polite".disabled erhalten; der Formularzustand kann mit aria-busy="true" vermittelt werden.role="status". Änderungen des Textes in einem deaktivierten Button werden von Hilfstechnologien nicht immer zuverlässig angekündigt.Ist der Button tatsächlich deaktiviert, muss sich sein visueller Zustand klar unterscheiden. Cursor und Transparenz sind nur visuelle Hinweise; der Grund für die nicht mögliche Übermittlung muss weiterhin textlich verständlich sein.
.form-submit:disabled {
cursor: not-allowed;
opacity: 0.6;
}disabled-Werten oder Einschränkungen des Interfaces.POST über HTTPS gesendet. CSRF-Schutz, Ratenbegrenzung, Idempotenz und weitere Maßnahmen richten sich nach dem Bedrohungsmodell und der Authentifizierung des Handlers.Promise.allSettled() meldet die Ergebnisse mehrerer unabhängiger Promises, macht sie aber weder zu einer atomaren Transaktion noch macht es bereits ausgeführte Aktionen bei einem Teilausfall rückgängig. Der Server legt fest, was als angenommen gilt, wie fehlgeschlagene Zustellungen wiederholt und Duplikate verhindert werden.Beispiel für die Übermittlung an einen serverseitigen Handler:
const form = document.querySelector('[data-contact-form]');
const submitButton = form.querySelector('[type="submit"]');
const formStatus = form.querySelector('[data-form-status]');
let isSubmitting = false;
form.addEventListener('submit', (event) => {
event.preventDefault();
if (isSubmitting || !form.reportValidity()) return;
isSubmitting = true;
submitButton.disabled = true;
form.setAttribute('aria-busy', 'true');
formStatus.textContent = 'Wird gesendet…';
fetch(form.action, {
method: 'POST',
body: new FormData(form),
headers: { Accept: 'application/json' },
})
.then((response) => {
if (!response.ok) throw new Error('Form submission failed');
form.reset();
formStatus.textContent = 'Das Formular wurde erfolgreich gesendet.';
})
.catch(() => {
formStatus.textContent = 'Das Formular konnte nicht gesendet werden. Verbindung prüfen und erneut versuchen.';
})
.then(() => {
isSubmitting = false;
submitButton.disabled = false;
form.removeAttribute('aria-busy');
});
});Das Beispiel setzt data-contact-form, einen Absende-Button und einen Statusbereich im Markup voraus. In einer produktiven Implementierung werden außerdem Serverfehler, Zeitüberschreitungen und Wiederholungen behandelt; clientseitiger Code kommuniziert nicht direkt mit externen Integrationen.
<form> wird geprüft; <fieldset> und <legend> gruppieren nur zusammengehörige Felder.id, einen passenden type, soweit anwendbar, und einen name, wenn sein Wert übermittelt werden soll.required-Beschränkungen, Feldformate, autocomplete und die Abgrenzung zwischen Beschriftung und optionalem placeholder werden geprüft.Tab, Shift+Tab, Leertaste für Kontrollkästchen sowie Tastaturverhalten nativer und eigener Auswahllisten.Ähnliche Kategorien:
Meine Vorstellung davon, wie jede React-Komponente aussehen sollte
421
8 min.
Der SST-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Registerkarten fest
62
6 min.
Der SSF-U-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Vollbildmodus fest
248
2 min.
Der SSA-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Akkordeons fest
258
2 min.
Analyse kritischer Fehler im Webdesign. Warum Slider, Autoplay und schwerfällige Seiten die Konversionsrate sowie die Platzierungen bei Google verschlechtern
232
2 min.
Der SSP-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Paginierung fest
240
1 min.