<div class="rtds-background-02 rtds-p-6 rtds-border rtds-border-gray-01">
    <div class="rtds-pb-6 rtds-space-y-4 rtds-border-b rtds-border-gray-01">
        <p>I campi obbligatori sono contrassegnati da un asterisco
            <abbr class="rtds-font-bold rtds-no-underline" title="obbligatorio">*</abbr>
        </p>
    </div>

    <form novalidate id="hasValidationForm" class="has-js-validation-form rtds-py-6 rtds-space-y-4">
        <div class="rtds-flex rtds-gap-4">
            <div class="rtds-w-1/2">
                <div class="rtds-input-field rtds-gap-1 ">
                    <label for="fieldName" class="rtds-input-field__label">
                        Nome

                        <abbr class="rtds-font-bold rtds-no-underline" title="obbligatorio">*</abbr>

                    </label>

                    <div class="rtds-input">
                        <input id="fieldName" class="rtds-text-sm rtds-input-placeholder is-name" type="text" data-label="Nome" aria-required="true" autocomplete="given-name" aria-describedBy="fieldNameError">
                    </div>

                    <span class="rtds-input-field__hint">

                        <span class="rtds-input-field__error" id="fieldNameError" aria-live="assertive">

                        </span>

                    </span>

                </div>
            </div>
            <div class="rtds-w-1/2">
                <div class="rtds-input-field rtds-gap-1 ">
                    <label for="fieldDocumentType" class="rtds-input-field__label">
                        Tipo di documento

                    </label>

                    <div class="rtds-input">
                        <input id="fieldDocumentType" class="rtds-text-sm rtds-input-placeholder is-documentType" type="text" data-label="Tipo di documento">
                    </div>

                </div>
            </div>
        </div>
        <div>
            <div class="rtds-input-field rtds-gap-1 ">
                <label for="fieldSurname" class="rtds-input-field__label">
                    Cognome

                    <abbr class="rtds-font-bold rtds-no-underline" title="obbligatorio">*</abbr>

                </label>

                <div class="rtds-input">
                    <input id="fieldSurname" class="rtds-text-sm rtds-input-placeholder is-familyname" type="text" data-label="Cognome" aria-required="true" autocomplete="family-name" aria-describedBy="fieldSurnameError">
                </div>

                <span class="rtds-input-field__hint">

                    <span class="rtds-input-field__error" id="fieldSurnameError" aria-live="assertive">

                    </span>

                </span>

            </div>
            <div class="rtds-input-field rtds-gap-1 ">
                <label for="fieldEmail" class="rtds-input-field__label">
                    Email

                    <abbr class="rtds-font-bold rtds-no-underline" title="obbligatorio">*</abbr>

                </label>

                <div class="rtds-input">
                    <input id="fieldEmail" class="rtds-text-sm rtds-input-placeholder is-email" type="email" data-label="Email" aria-required="true" autocomplete="email" aria-describedBy="fieldEmailError">
                </div>

                <span class="rtds-input-field__hint">

                    <span class="rtds-input-field__error" id="fieldEmailError" aria-live="assertive">

                    </span>

                </span>

            </div>

            <div class="rtds-input rtds-input-field rtds-input-checkbox has-js-validation-required is-privacy">

                <div class="rtds-flex rtds-gap-2 rtds-items-center">
                    <input id="fieldPrivacy" name="fieldPrivacy" data-label="Privacy policy" class="   " type="checkbox" aria-required="true" aria-describedBy="fieldPrivacyError">
                    <label class="rtds-input-checkbox__label" for="fieldPrivacy">
                        Dichiaro di aver letto la <a href="" target="_blank" class="rtds-action-link-primary rtds-underline rtds-font-bold">Privacy Policy<span class="rtds-sr-only"> (apre una nuova tab o finestra del browser)</span></a> e di accettarne i contenuti.
                        <abbr class="rtds-font-bold rtds-no-underline" title="obbligatorio">*</abbr></label>
                </div>

                <span class="rtds-input-field__hint">

                    <span class="rtds-input-field__error" id="fieldPrivacyError" aria-live="assertive">

                    </span>

                </span>

            </div>
        </div>

        <div class="rtds-form-feedback">
            <p class="rtds-form-feedback-sr rtds-sr-only" aria-live="assertive"><!-- js --></p>
            <p class="rtds-form-feedback-msg" aria-hidden="true"></p>
        </div>

        <div class="rtds-form-actions">
            <button type="submit" class="rtds-btn 
    rtds-btn--primary
     rtds-form-submit">

                Registrati

            </button>

        </div>
    </form>
</div>
<div class="rtds-background-02 rtds-p-6 rtds-border rtds-border-gray-01">
        <div class="rtds-pb-6 rtds-space-y-4 rtds-border-b rtds-border-gray-01">
            <p>I campi obbligatori sono contrassegnati da un asterisco
            <abbr class="rtds-font-bold rtds-no-underline" title="obbligatorio">*</abbr></p>
        </div>

        <form novalidate id="hasValidationForm" class="has-js-validation-form rtds-py-6 rtds-space-y-4">
            <div class="rtds-flex rtds-gap-4">
                <div class="rtds-w-1/2">
                    {% render '@input-field', { label: 'Nome', inputShortLabel: 'Nome',inputClasses:'is-name', inputId: 'fieldName', inputType: 'text', inputAutocomplete: 'given-name', inputRequired: 'true'  }, true %}
                </div>
                <div class="rtds-w-1/2">
                    {% render '@input-field', { label: 'Tipo di documento', inputShortLabel: 'Tipo di documento', inputClasses:'is-documentType', inputId: 'fieldDocumentType' }, true %}
                </div>
            </div>
            <div>
            {% render '@input-field', { label: 'Cognome', inputShortLabel: 'Cognome',inputClasses:'is-familyname', inputId: 'fieldSurname', inputType: 'text', inputAutocomplete: 'family-name', inputRequired: 'true' }, true %}
            {% render '@input-field', { label: 'Email', inputShortLabel: 'Email', inputClasses:'is-email', inputId: 'fieldEmail', inputType: 'email', inputAutocomplete: 'email', inputRequired: 'true' }, true %}
            {% render '@input-checkbox', { label: 'Dichiaro di aver letto la <a href="" target="_blank" class="rtds-action-link-primary rtds-underline rtds-font-bold">Privacy Policy<span class="rtds-sr-only"> (apre una nuova tab o finestra del browser)</span></a> e di accettarne i contenuti.', shortLabel: 'Privacy policy', classes:'has-js-validation-required is-privacy', id: 'fieldPrivacy', fieldRequired: 'true' }, true %}
            </div>

            <div class="rtds-form-feedback">
                <p class="rtds-form-feedback-sr rtds-sr-only" aria-live="assertive"><!-- js --></p>
                <p class="rtds-form-feedback-msg" aria-hidden="true"></p>
            </div>

            <div class="rtds-form-actions">
                {% render '@button', { label: 'Registrati', buttonType: 'submit', classes: 'rtds-form-submit' }, true %}
            </div>
        </form>
    </div>
/* No context defined. */
  • Content:
    /**
     * FORM WITH VALIDATION
     * FEEDBACK MESSAGE
     *
    */
    @layer components {
        .rtds-form-feedback-msg.is-invalid {
            @apply rtds-p-3 rtds-bg-error-light rtds-content-error rtds-font-bold rtds-border rtds-border-error-dark rtds-rounded-md;
        }
    
        .rtds-form-feedback-msg.is-success {
            @apply rtds-p-3 rtds-bg-success-light rtds-content-success rtds-font-bold rtds-border rtds-border-success-dark rtds-rounded-md;
        }
    }
  • URL: /components/raw/form-with-validation/form-with-validation.css
  • Filesystem Path: components/04-organisms/form-with-validation/form-with-validation.css
  • Size: 436 Bytes
  • Content:
    document.addEventListener("DOMContentLoaded", function () {
      ///* Regex per verifica email */
      const emailRegex = /\S+@\S+\.\S+/; // has @ and .
    
      ///* Form */
      const elForm = document.getElementById("hasValidationForm");
    
      if (elForm) {
    
        ///* Campi form */
        const elRequiredName = document.getElementsByClassName("is-name")[0];
        const elRequiredFamilyname = document.getElementsByClassName("is-familyname")[0];
        const elPrivacy = document.getElementsByClassName("is-privacy")[0];
        const elEmail = document.getElementsByClassName("is-email")[0];
    
        ///* Errori form da verificare */
        const formErrors = {
          name: false,
          familyname: false,
          email: false,
          privacy: false
        };
    
        let hasSubmitted = false;
    
        validateField({
          elField: elEmail,
          validateFn: validateFieldEmail
        });
    
        validateField({
          elField: elRequiredName,
          validateFn: validateFieldRequired,
          errorKey: 'name'
        });
    
        validateField({
          elField: elRequiredFamilyname,
          validateFn: validateFieldRequired,
          errorKey: 'familyname'
        });
    
        validateField({
          elField: elPrivacy,
          validateFn: validateFieldPrivacy
        });
    
        // Gestione validazione su 3 eventi: change, blur, keyup
        function validateField({ elField, validateFn, errorKey }) {
          let touched = false;
    
          elField.addEventListener("change", (e) => {
            touched = true; // mark it as touched so that on blur it shows the error.
            validateFn(e.target, { live: true, errorKey });
            if (hasSubmitted) {
              updateSubmitSummary();
            }
          });
    
          elField.addEventListener("keyup", (e) => {
            // remove any error on keyup if existent
            validateFn(e.target, { removeOnly: true, errorKey });
    
            if (hasSubmitted) {
              updateSubmitSummary();
            }
          });
    
          elField.addEventListener("blur", (e) => {
            if (!touched) return;
            // show error if touched
            validateFn(e.target, { live: true, errorKey });
          });
        }
    
        // Controllo email
        function validateFieldEmail(el, opts) {
          const isEmpty = el.value === "";
          updateFieldDOM(el, !isEmpty, "Email obbligatoria.", opts);
    
          if (isEmpty) {
            formErrors.email = true;
          } else {
            const isEmailValid = el.value.match(emailRegex);
            updateFieldDOM(el, isEmailValid, "Email non valida.", opts);
            formErrors.email = !isEmailValid;
          }
        }
    
        // Validazione campi obbligatori
        function validateFieldRequired(el, opts) {
          const isEmpty = el.value === "";
          const errorKey = opts?.errorKey;
          const elField = el.closest(".rtds-input-field");
          const elLabel = elField.querySelector(".rtds-input-field__label-text");
          const fieldLabel = elLabel ? elLabel.innerText : "Field";
    
          updateFieldDOM(el, !isEmpty, `${fieldLabel} obbligatorio.`, opts);
    
          if (errorKey) {
            formErrors[errorKey] = isEmpty;
          }
        }
    
        // Validazione campo privacy (checkbox)
        function validateFieldPrivacy(el, opts) {
          const isNotChecked = el.checked === false;
          updateFieldDOM(el, !isNotChecked, "Devi dichiarare di aver letto la Privacy Policy.", opts);
    
          formErrors.privacy = isNotChecked;
        }
    
        // Aggiornamento errore nel campo e gestione attributi accessibilità
        function updateFieldDOM(el, isValid, errorMessage, opts) {
          const removeOnly = opts?.removeOnly;
          const isLive = opts?.live;
          const elField = el.closest(".rtds-input-field");
          const elError = elField.querySelector(".rtds-input-field__error");
    
          if (isValid) {
            elField.classList.remove("is-invalid");
            elError.innerText = ""; // It's valid
            el.removeAttribute("aria-invalid");
          } else if (!removeOnly) {
            elField.classList.add("is-invalid");
            el.setAttribute("aria-invalid", "true");
            elError.setAttribute("aria-live", isLive ? "assertive" : "off");
            elError.innerText = errorMessage;
          }
        }
    
        // Aggiornamento feedback form a invio
        function updateSubmitSummary({ isSubmit } = {}) {
          const elSummary = elForm.querySelector(".rtds-form-feedback");
          const elSummaryMsg = elSummary.querySelector(".rtds-form-feedback-msg");
    
          // Clear form feedback
          elSummaryMsg.classList.remove("is-invalid");
          elSummaryMsg.classList.remove("is-success");
          elSummaryMsg.innerText = "";
          const errorsState = Object.entries(formErrors);
    
          const invalidFields = errorsState.filter(([key, value]) => value === true).map(([key]) => {
            const elField = elForm.querySelector(`.is-${key}`);
            return elField ? elField.getAttribute('data-label') : key;
          });
    
          if (invalidFields.length > 0) {
            // Show error msg
            const errorCount = invalidFields.length;
            const errorMsg =
              errorCount === 1
                ? `È presente ${errorCount} campo non valido: ${invalidFields.join(', ')}.`
                : `Sono presenti ${errorCount} campi non validi: ${invalidFields.join(', ')}.`;
    
            elSummaryMsg.classList.add("is-invalid");
            elSummaryMsg.innerText = errorMsg;
    
            elSummary.querySelector(".rtds-form-feedback-sr").innerText = isSubmit
              ? // Set SR error message only on submit to avoid being re-announced
              // every time the error summary visually changes.
              errorMsg
              : "";
          } else if (isSubmit) {
            const successMsg = "Form inviata con successo.";
            elSummary.querySelector(".rtds-form-feedback-sr").innerText = successMsg;
            elSummaryMsg.innerText = successMsg;
            elSummaryMsg.classList.add("is-success");
          }
        }
    
        elForm.addEventListener("submit", (e) => {
          e.preventDefault();
          hasSubmitted = true;
    
          // Validate again
          validateFieldEmail(elEmail);
          validateFieldRequired(elRequiredName, { errorKey: 'name' });
          validateFieldRequired(elRequiredFamilyname, { errorKey: 'familyname' });
          validateFieldPrivacy(elPrivacy);
    
          updateSubmitSummary({ isSubmit: true });
        });
      }
    });
    
  • URL: /components/raw/form-with-validation/form-with-validation.js
  • Filesystem Path: components/04-organisms/form-with-validation/form-with-validation.js
  • Size: 6.1 KB

Form with validation

Esempio di form con validazione client-side accessibile. Questo file ha uno scopo indicativo e necessita di customizzazioni per il suo utilizzo.

Caratteristiche principali

  • Validazione client-side in tempo reale
  • Feedback accessibile per utenti con screen reader
  • Gestione degli errori con messaggi chiari e contestuali
  • Supporto per campi obbligatori e validazione email
  • Feedback visivo e testuale per ogni campo
  • Riepilogo degli errori a livello di form

Struttura del form

Il form include:

  • Campo Nome (obbligatorio)
  • Campo Cognome (obbligatorio)
  • Campo Email (obbligatorio, con validazione formato)
  • Campo Tipo documento (opzionale)
  • Checkbox Privacy Policy (obbligatoria)

Validazione

La validazione avviene su tre eventi:

  1. change: quando l’utente modifica il valore
  2. blur: quando l’utente lascia il campo
  3. keyup: per rimuovere gli errori durante la digitazione

Accessibilità

Il componente implementa le seguenti caratteristiche di accessibilità:

  • Attributi ARIA appropriati (aria-invalid, aria-live)
  • Messaggi di errore annunciati da screen reader
  • Riepilogo degli errori accessibile
  • Indicatori visivi per campi obbligatori
  • Feedback contestuali per ogni campo

Personalizzazione

Per utilizzare il componente:

  1. Copiare la struttura HTML del file
  2. Adattare i campi secondo le proprie necessità
  3. Mantenere le classi CSS e gli attributi ARIA per garantire l’accessibilità
  4. Personalizzare le parti di script necessarie (riferimenti dei campi ecc) e i messaggi di errore nel file JavaScript

Note tecniche

  • Utilizza regex per la validazione email
  • Implementa gestione degli errori per ogni tipo di campo
  • Fornisce feedback sia visivo che testuale
  • Supporta la validazione in tempo reale e al submit