Moduli Accessibili
Creare moduli utilizzabili da tutti con un'esperienza chiara e guidata
Etichette e associazioni
Esempio negativo
<!-- ❌ Negativo: Etichette mancanti -->
<form>
<h4>Accesso</h4>
<!-- Nessuna etichetta associata -->
<input
type="email"
placeholder="La tua email">
<!-- Nessuna etichetta associata, nessun autocompletamento -->
<input
type="password"
placeholder="Password">
<!-- Casella di controllo non associata -->
<input type="checkbox">
<span>Ricordami</span>
<button type="submit">Accedi</button>
</form>Esempio positivo
<!-- ✅ Positivo: Etichette esplicite e associate -->
<form>
<h4>Accesso</h4>
<!-- Etichetta associata tramite for/id -->
<label for="email-good">Indirizzo email</label>
<input
type="email"
id="email-good"
placeholder="esempio@dominio.com"
required
aria-describedby="email-help"
autocomplete="email">
<div id="email-help">
Non condivideremo mai la tua email
</div>
<!-- Etichetta con istruzioni -->
<label for="password-good">Password</label>
<input
type="password"
id="password-good"
required
aria-describedby="password-help"
autocomplete="current-password">
<div id="password-help">
Minimo 8 caratteri con maiuscole e numeri
</div>
<!-- Casella di controllo correttamente associata -->
<input type="checkbox" id="remember-good">
<label for="remember-good">Ricordami</label>
<button type="submit">Accedi</button>
</form>Spiegazione: Ogni campo del modulo deve avere un'etichetta esplicita e correttamente associata. I placeholder non sostituiscono le etichette.
Validazione e messaggi di errore
Esempio negativo
<!-- ❌ Negativo: Errori annunciati male -->
<form @submit.prevent="submitBadForm">
<h4>Iscrizione newsletter</h4>
<!-- Nessuna associazione etichetta-input -->
<label>Nome</label>
<input
type="text"
v-model="badForm.name"
:class="{ 'error': submitted && !badForm.name }">
<!-- Solo errore visivo -->
<label>Email</label>
<input
type="email"
v-model="badForm.email"
:class="{ 'error': submitted && !isValidEmail(badForm.email) }">
<!-- Nessuna informazione sull'errore -->
<label>Età</label>
<input
type="number"
v-model="badForm.age"
:class="{ 'error': submitted && badForm.age < 16 }">
<!-- Messaggio di errore generico e inutile -->
<div v-if="submitted && badFormErrors.length">
❌ Correggi gli errori
</div>
<button type="submit">Iscriviti</button>
</form>Esempio positivo
<!-- ✅ Positivo: Errori chiaramente associati -->
<form @submit.prevent="submitGoodForm" novalidate>
<h4>Iscrizione newsletter</h4>
<!-- Riepilogo errori con navigazione -->
<div
v-if="submitted && goodFormErrors.length"
role="alert"
aria-labelledby="error-summary-title">
<h5 id="error-summary-title">Errori da correggere:</h5>
<ul>
<li v-for="error in goodFormErrors" :key="error.field">
<a :href="`#${error.field}-good-form`">
</a>
</li>
</ul>
</div>
<!-- Campo con errore specifico associato -->
<label for="name-good-form">Nome completo *</label>
<input
type="text"
id="name-good-form"
v-model="goodForm.name"
required
aria-invalid="submitted && !goodForm.name"
aria-describedby="name-error">
<div
v-if="submitted && !goodForm.name"
id="name-error"
role="alert">
❌ Il nome è obbligatorio
</div>
<button type="submit">Iscriviti</button>
</form>Spiegazione: Gli errori devono essere chiaramente annunciati, associati ai campi pertinenti e fornire istruzioni precise per la correzione.
Gruppi di campi con aria-labelledby
Esempio negativo
<!-- ❌ Negativo: Struttura disorganizzata -->
<form>
<h4>Preferenze</h4>
<!-- Elementi misti senza logica -->
<div>
<input type="radio" name="contact-bad"
id="email-contact-bad" value="email">
<label for="email-contact-bad">Email</label>
<input type="checkbox" id="newsletter-bad"
value="newsletter">
<label for="newsletter-bad">Newsletter</label>
</div>
<div>
<input type="radio" name="contact-bad"
id="phone-contact-bad" value="phone">
<label for="phone-contact-bad">Telefono</label>
<!-- Campo di testo senza contesto -->
<input type="text" placeholder="Nome">
</div>
<div>
<input type="checkbox" id="offers-bad" value="offers">
<label for="offers-bad">Offerte</label>
<input type="radio" name="contact-bad"
id="sms-contact-bad" value="sms">
<label for="sms-contact-bad">SMS</label>
</div>
<!-- Select senza contesto -->
<select>
<option>Scegli...</option>
<option>Giornaliera</option>
<option>Settimanale</option>
</select>
<button type="submit">OK</button>
</form>Esempio positivo
<!-- ✅ Positivo: Gruppi logici con aria-labelledby -->
<form>
<h4>Preferenze di contatto</h4>
<!-- Sezione informazioni personali -->
<div role="group" aria-labelledby="personal-info-title">
<h5 id="personal-info-title">
Informazioni personali
</h5>
<label for="name-good">Nome completo *</label>
<input
type="text"
id="name-good"
name="name"
required
aria-required="true"
aria-describedby="name-desc">
<span id="name-desc">
Il tuo nome completo come apparirà nelle nostre comunicazioni
</span>
</div>
<!-- Gruppo di pulsanti radio -->
<div
role="radiogroup"
aria-labelledby="contact-method-title"
aria-required="true">
<h5 id="contact-method-title">
Metodo di contatto preferito *
</h5>
<input
type="radio"
name="contact-good"
id="email-contact-good"
value="email"
required>
<label for="email-contact-good">Email</label>
<input
type="radio"
name="contact-good"
id="phone-contact-good"
value="phone"
required>
<label for="phone-contact-good">Telefono</label>
</div>
<!-- Gruppo di caselle di controllo -->
<div role="group" aria-labelledby="comm-types-title">
<h5 id="comm-types-title">Tipi di comunicazioni</h5>
<p id="comm-types-desc">
Seleziona i tipi di comunicazioni che desideri ricevere
</p>
<input
type="checkbox"
id="newsletter-good"
value="newsletter"
aria-describedby="comm-types-desc">
<label for="newsletter-good">Newsletter</label>
</div>
<button type="submit">
Salva preferenze
</button>
</form>Spiegazione: I gruppi di campi correlati devono essere associati ai loro titoli tramite aria-labelledby per creare relazioni semantiche chiare, in particolare per i pulsanti radio e le caselle di controllo.
Modulo complesso con validazione in tempo reale
Esempio negativo
<!-- ❌ Negativo: Validazione intrusiva -->
<form>
<h4>Crea account</h4>
<!-- Validazione immediata ad ogni digitazione -->
<label>Nome utente</label>
<input
type="text"
v-model="complexBadForm.username"
@input="validateUsernameBad"
:class="{ 'error': usernameBadError }">
<!-- Errore che appare/scompare costantemente -->
<div v-if="usernameBadError">
</div>
<!-- Nessuna associazione con aria-describedby -->
<label>Email</label>
<input
type="email"
v-model="complexBadForm.email"
@input="validateEmailBad"
:class="{ 'error': emailBadError }">
<div v-if="emailBadError"></div>
<!-- Messaggi di errore troppo brevi e non utili -->
<!-- Nessun autocompletamento per aiutare i gestori di password -->
<label>Password</label>
<input
type="password"
v-model="complexBadForm.password"
@input="validatePasswordBad"
:class="{ 'error': passwordBadError }">
<div v-if="passwordBadError"></div>
<button type="submit">Crea account</button>
</form>Esempio positivo
<!-- ✅ Positivo: Validazione rispettosa con feedback -->
<form>
<h4>Crea account</h4>
<!-- Validazione su blur, non su input -->
<label for="username-complex">Nome utente *</label>
<input
type="text"
id="username-complex"
v-model="complexGoodForm.username"
@blur="validateUsernameGood"
:class="{ error: usernameGoodError,
success: usernameGoodValid }"
aria-describedby="username-requirements username-feedback"
aria-invalid="!!usernameGoodError"
required>
<!-- Istruzioni chiare -->
<div id="username-requirements">
<h6>Requisiti:</h6>
<ul>
<li :class="{ valid: complexGoodForm.username.length >= 3 }">
Almeno 3 caratteri
</li>
<li :class="{ valid: /^[a-zA-Z0-9_]+$/.test(username) }">
Solo lettere, numeri e underscore
</li>
</ul>
</div>
<!-- Feedback accessibile -->
<div
v-if="usernameGoodError"
id="username-feedback"
role="alert"
aria-live="polite">
❌
</div>
<div
v-else-if="usernameGoodValid"
id="username-feedback"
role="status"
aria-live="polite">
✅ Nome utente disponibile
</div>
<!-- Indicatore di forza della password -->
<label for="password-complex">Password *</label>
<input
type="password"
id="password-complex"
v-model="complexGoodForm.password"
@input="validatePasswordGood"
aria-describedby="password-strength"
autocomplete="new-password"
required>
<div id="password-strength">
<h6>Forza della password:</h6>
<div class="strength-indicator">
<div
class="strength-bar"
:class="`strength-${passwordStrength}`"
:style="{ width: `${passwordStrength * 25}%` }">
</div>
</div>
</div>
<button type="submit" :disabled="!isFormValid">
Crea account
</button>
</form>Spiegazione: Per i moduli complessi, la validazione in tempo reale può aiutare, ma deve essere implementata in modo non intrusivo e accessibile.