Wie man ein HTML-Formular mit Client-seitiger Validierung in JavaScript erstellt

Die Client-seitige Validierung dient dazu, die Nutzererfahrung zu verbessern: Sie zeigt Fehler sofort an, reduziert unnötige Einsendungen und macht das Formular „benutzerfreundlicher“. Aber sie ist keine Sicherheitsmaßnahme: Jede Prüfung muss trotzdem serverseitig wiederholt werden. Trotzdem ist eine gut gemachte Client-Validierung eines dieser Details, die die Konversionen wirklich verändern (und „unsaubere“ Kontaktanfragen reduzieren).

In diesem Leitfaden zeigen wir, wie man ein modernes HTML-Formular mit Client-seitiger Validierung in JavaScript erstellt, unter Verwendung der nativen Browserwerkzeuge (Constraint Validation API) und mit benutzerdefinierten Nachrichten, Fehlerverwaltung, Barrierefreiheit und einer sauberen UX.

Programmazione JavaScript
Programmazione JavaScript
FullPress auf Google folgen

Füge uns deinen bevorzugten Quellen hinzu.

Dieser Quelle auf Google folgen

Die Client-seitige Validierung dient dazu, die Nutzererfahrung zu verbessern: Sie zeigt Fehler sofort an, reduziert unnötige Einsendungen und macht das Formular „benutzerfreundlicher“. Aber sie ist keine Sicherheitsmaßnahme: Jede Prüfung muss trotzdem serverseitig wiederholt werden. Trotzdem ist eine gut gemachte Client-Validierung eines dieser Details, die die Konversionen wirklich verändern (und „unsaubere“ Kontaktanfragen reduzieren).

In diesem Leitfaden zeigen wir, wie man ein modernes HTML-Formular mit Client-seitiger Validierung in JavaScript erstellt, unter Verwendung der nativen Browserwerkzeuge (Constraint Validation API) und mit benutzerdefinierten Nachrichten, Fehlerverwaltung, Barrierefreiheit und einer sauberen UX.

Was bedeutet „Client-seitige Validierung“

Client-seitige Validierung bedeutet, die Daten vor dem Absenden des Formulars an den Server zu überprüfen. Beispiele:

  • leeres oder ungültiges E-Mail-Feld
  • zu kurzes Passwort
  • Checkbox „Datenschutz akzeptieren“ nicht ausgewählt
  • Telefonnummer im falschen Format

Der beste Weg, dies zu tun, ist eine Kombination aus:

  • HTML5-Validierung (required, type, minlength, pattern usw.)
  • JavaScript zur Verwaltung von UI, benutzerdefinierten Nachrichten und zusätzlicher Logik

Goldene Regel: Client-seitige UX, serverseitige Sicherheit

Denke daran: Der Nutzer kann JS deaktivieren, HTML verändern oder Anfragen direkt per API senden. Daher:

  • dient die Client-Validierung der UX und Datenqualität
  • dient die Server-Validierung der Sicherheit und Integrität

Vollständiges Beispiel: Formular mit Validierung und benutzerdefinierten Nachrichten

Unten findest du ein fertiges Beispiel: Name, E-Mail, Telefon, Nachricht und Datenschutz-Einwilligung. Wir verwenden:

  • Constraint Validation API (checkValidity, reportValidity, validity)
  • Fehlermeldungen pro Einzel-Feld
  • CSS-Klassen für gültigen/ungültigen Zustand
  • Barrierefreiheit mit aria-describedby und aria-live
<form id="contactForm" novalidate>
  <div class="field">
    <label for="name">Nome e cognome</label>
    <input id="name" name="name" type="text" required minlength="2"
           autocomplete="name" aria-describedby="nameError">
    <p id="nameError" class="error" aria-live="polite"></p>
  </div>

  <div class="field">
    <label for="email">Email</label>
    <input id="email" name="email" type="email" required
           autocomplete="email" aria-describedby="emailError">
    <p id="emailError" class="error" aria-live="polite"></p>
  </div>

  <div class="field">
    <label for="phone">Telefono (opzionale)</label>
    <input id="phone" name="phone" type="tel"
           inputmode="tel"
           pattern="^\\+?[0-9\\s\\-]{7,20}$"
           placeholder="+39 348 123 4567"
           autocomplete="tel" aria-describedby="phoneHelp phoneError">
    <small id="phoneHelp" class="help">Formato ammesso: numeri, spazi e trattini.</small>
    <p id="phoneError" class="error" aria-live="polite"></p>
  </div>

  <div class="field">
    <label for="message">Messaggio</label>
    <textarea id="message" name="message" required minlength="10" rows="5"
              aria-describedby="messageError"></textarea>
    <p id="messageError" class="error" aria-live="polite"></p>
  </div>

  <div class="field">
    <label class="checkbox">
      <input id="privacy" name="privacy" type="checkbox" required aria-describedby="privacyError">
      Ho letto e accetto l’informativa privacy
    </label>
    <p id="privacyError" class="error" aria-live="polite"></p>
  </div>

  <button type="submit">Invia</button>

  <p id="formStatus" class="status" aria-live="polite"></p>
</form>

Minimale (optionale) Styles, um die Zustände klar zu machen:

.field { margin-bottom: 14px; }
label { display:block; margin-bottom: 6px; font-weight: 600; }
input, textarea { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 8px; }
input.is-invalid, textarea.is-invalid { border-color: #c0392b; }
input.is-valid, textarea.is-valid { border-color: #27ae60; }
.error { margin: 6px 0 0; color: #c0392b; font-size: 0.95rem; }
.help { display:block; margin-top: 6px; color:#666; font-size: 0.9rem; }
.status { margin-top: 12px; }

Und jetzt der JavaScript-Teil: Validierung beim Absenden + „Live“-Validierung während der Eingabe.

(() => {
  const form = document.getElementById('contactForm');
  const statusEl = document.getElementById('formStatus');

  // Mappa campo -> elemento errori
  const errorMap = new Map([
    ['name', document.getElementById('nameError')],
    ['email', document.getElementById('emailError')],
    ['phone', document.getElementById('phoneError')],
    ['message', document.getElementById('messageError')],
    ['privacy', document.getElementById('privacyError')],
  ]);

  const getErrorMessage = (input) => {
    const v = input.validity;

    if (v.valueMissing) {
      // Messaggi specifici per campo
      if (input.name === 'privacy') return 'Devi accettare l’informativa privacy per proseguire.';
      return 'Questo campo è obbligatorio.';
    }
    if (v.typeMismatch) {
      if (input.type === 'email') return 'Inserisci un indirizzo email valido.';
      return 'Formato non valido.';
    }
    if (v.tooShort) {
      return `Inserisci almeno ${input.minLength} caratteri.`;
    }
    if (v.patternMismatch) {
      if (input.name === 'phone') return 'Inserisci un numero valido (numeri, spazi e trattini).';
      return 'Formato non valido.';
    }
    if (v.badInput) return 'Valore non valido.';
    return '';
  };

  const setFieldState = (input, isValid, message = '') => {
    const errorEl = errorMap.get(input.name);
    if (errorEl) errorEl.textContent = message;

    input.classList.toggle('is-invalid', !isValid);
    input.classList.toggle('is-valid', isValid);

    // Per accessibilità: aria-invalid quando non valido
    input.setAttribute('aria-invalid', String(!isValid));
  };

  const validateField = (input) => {
    // telefono opzionale: se vuoto, consideralo valido
    if (input.name === 'phone' && input.value.trim() === '') {
      setFieldState(input, true, '');
      return true;
    }

    const ok = input.checkValidity();
    const msg = ok ? '' : getErrorMessage(input);
    setFieldState(input, ok, msg);
    return ok;
  };

  const focusFirstInvalid = () => {
    const firstInvalid = form.querySelector('.is-invalid');
    if (firstInvalid) firstInvalid.focus({ preventScroll: false });
  };

  // Validazione live: al blur e all'input
  form.addEventListener('blur', (e) => {
    const target = e.target;
    if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
      validateField(target);
    }
  }, true);

  // Debounce semplice per non validare a ogni tasto in modo aggressivo
  let t = null;
  form.addEventListener('input', (e) => {
    const target = e.target;
    if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return;

    clearTimeout(t);
    t = setTimeout(() => validateField(target), 200);
  });

  form.addEventListener('submit', async (e) => {
    e.preventDefault();
    statusEl.textContent = '';

    const inputs = Array.from(form.querySelectorAll('input, textarea'));
    const allOk = inputs.every(validateField);

    if (!allOk) {
      statusEl.textContent = 'Controlla i campi evidenziati.';
      focusFirstInvalid();
      return;
    }

    // Qui invieresti i dati al server (fetch/AJAX) oppure lasci fare al form
    // Esempio placeholder:
    statusEl.textContent = 'Invio in corso...';

    try {
      // Simulazione invio
      await new Promise((r) => setTimeout(r, 400));
      statusEl.textContent = 'Messaggio inviato correttamente.';
      form.reset();

      // Pulisci stati visivi dopo reset
      inputs.forEach((i) => {
        i.classList.remove('is-valid', 'is-invalid');
        i.removeAttribute('aria-invalid');
        const err = errorMap.get(i.name);
        if (err) err.textContent = '';
      });
    } catch (err) {
      statusEl.textContent = 'Errore durante l’invio. Riprova.';
    }
  });
})();

Warum die Constraint Validation API nutzen

Viele erfinden das Rad mit Regex überall und manuellen Kontrollen neu. Tatsächlich bieten moderne Browser ein robustes, fertiges System:

  • checkValidity() prüft die Gültigkeit des Feldes ohne Browser-Popup anzuzeigen
  • reportValidity() kann native Nachrichten anzeigen (praktisch in einfachen Fällen)
  • validityzeigt den detaillierten Status an (valueMissing, tooShort, typeMismatch, patternMismatch…)

So kannst du dich auf die UX konzentrieren (Nachrichten, Hervorhebung, Barrierefreiheit), ohne unnötige Logiken zu duplizieren.

„Sanfte“ Validierung: wann der Fehler angezeigt werden soll

Ein zu früh angezeigter Fehler kann störend sein. Praktische Richtlinien:

  • zeige Fehler bei Absenden (immer)
  • zeige Fehler beim Verlassen des Feldes (blur) (wenn der Benutzer das Feld verlässt)
  • validiere „live“ während der Eingabe nur mit Debounce und nur, wenn das Feld bereits angetippt wurde (fortgeschrittener Ansatz)

Im Beispiel validieren wir beim Absenden alles; während der Eingabe validieren wir beim Blur und mit einem leichten Debounce.

Barrierefreiheit: keine Option

Zwei Dinge machen den Unterschied:

  • verbinde die Fehlermeldung mit dem Eingabefeld mittels aria-describedby
  • aktualisiere den Status mit aria-invalid wenn nötig

Außerdem ermöglicht die Verwendung von aria-live="polite" auf dem Fehlermeldungstext Bildschirmlesern, die Änderung zu kommunizieren.

Schnelle FAQs

Muss ich novalidate im Formular verwenden?

Wenn du Nachrichten und UI selbst steuern möchtest, ja: novalidate deaktiviert die nativen Browser-Popups und ermöglicht eine konsistente UX. Wenn dir die Standard-HTML5-Validierung genügt, kannst du es auch weglassen.

Ist der Regex für die Telefonnummer Pflicht?

Nein. Telefonnummern sind komplex (verschiedene Formate). Besser eine permissive Validierung auf Client-Seite und Normalisierung auf Server-Seite oder dedizierte Bibliotheken, wenn hohe Präzision benötigt wird.

Kann ich mit fetch senden ohne die Seite neu zu laden?

Ja. Im Submit-Block findest du bereits eine Stelle, um fetch() einzufügen. Denk daran: immer Server-Validierung, Fehlerbehandlung und klare Meldungen auf Client-Seite.

Fazit

Ein Formular mit clientseitiger Validierung in JavaScript muss nicht kompliziert sein: HTML5 bietet dir bereits solide Regeln, JavaScript ermöglicht es dir, diese klarer, konsistenter und zugänglicher zu machen. Wenn du nützliche Meldungen erstellst, Fehler richtig hervorhebst und die Zugänglichkeit nicht vergisst, erhältst du schnellere Formulare, weniger Frustration und bessere Datenqualität.

Pubblicato in ,

Se vuoi rimanere aggiornato su Wie man ein HTML-Formular mit Client-seitiger Validierung in JavaScript erstellt iscriviti alla nostra newsletter settimanale

Hinterlasse jetzt einen Kommentar

Kommentar hinterlassen

E-Mail Adresse wird nicht veröffentlicht.


*