Toegankelijke Formulieren
Maak formulieren bruikbaar voor iedereen met een duidelijke en begeleide ervaring
Labels en associaties
Slecht voorbeeld
<!-- ❌ Slecht: Ontbrekende labels -->
<form>
<h4>Inloggen</h4>
<!-- Geen gekoppeld label -->
<input
type="email"
placeholder="Je e-mail">
<!-- Geen gekoppeld label, geen autocomplete -->
<input
type="password"
placeholder="Wachtwoord">
<!-- Selectievakje niet gekoppeld -->
<input type="checkbox">
<span>Onthoud mij</span>
<button type="submit">Inloggen</button>
</form>Goed voorbeeld
<!-- ✅ Goed: Expliciete en gekoppelde labels -->
<form>
<h4>Inloggen</h4>
<!-- Label gekoppeld via for/id -->
<label for="email-good">E-mailadres</label>
<input
type="email"
id="email-good"
placeholder="voorbeeld@domein.com"
required
aria-describedby="email-help"
autocomplete="email">
<div id="email-help">
We delen je e-mail nooit
</div>
<!-- Label met instructies -->
<label for="password-good">Wachtwoord</label>
<input
type="password"
id="password-good"
required
aria-describedby="password-help"
autocomplete="current-password">
<div id="password-help">
Minimaal 8 tekens met hoofdletters en cijfers
</div>
<!-- Selectievakje correct gekoppeld -->
<input type="checkbox" id="remember-good">
<label for="remember-good">Onthoud mij</label>
<button type="submit">Inloggen</button>
</form>Uitleg: Elk formulierveld moet een expliciet en correct geassocieerd label hebben. Placeholders vervangen geen labels.
Validatie en foutmeldingen
Slecht voorbeeld
<!-- ❌ Slecht: Slecht aangekondigde fouten -->
<form @submit.prevent="submitBadForm">
<h4>Nieuwsbriefaanmelding</h4>
<!-- Geen label-input koppeling -->
<label>Naam</label>
<input
type="text"
v-model="badForm.name"
:class="{ 'error': submitted && !badForm.name }">
<!-- Alleen visuele fout -->
<label>E-mail</label>
<input
type="email"
v-model="badForm.email"
:class="{ 'error': submitted && !isValidEmail(badForm.email) }">
<!-- Geen foutinformatie -->
<label>Leeftijd</label>
<input
type="number"
v-model="badForm.age"
:class="{ 'error': submitted && badForm.age < 16 }">
<!-- Generieke en nutteloze foutmelding -->
<div v-if="submitted && badFormErrors.length">
❌ Corrigeer de fouten
</div>
<button type="submit">Aanmelden</button>
</form>Goed voorbeeld
<!-- ✅ Goed: Duidelijk gekoppelde fouten -->
<form @submit.prevent="submitGoodForm" novalidate>
<h4>Nieuwsbriefaanmelding</h4>
<!-- Foutoverzicht met navigatie -->
<div
v-if="submitted && goodFormErrors.length"
role="alert"
aria-labelledby="error-summary-title">
<h5 id="error-summary-title">Te corrigeren fouten:</h5>
<ul>
<li v-for="error in goodFormErrors" :key="error.field">
<a :href="`#${error.field}-good-form`">
</a>
</li>
</ul>
</div>
<!-- Veld met specifieke gekoppelde fout -->
<label for="name-good-form">Volledige naam *</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">
❌ Naam is verplicht
</div>
<button type="submit">Aanmelden</button>
</form>Uitleg: Fouten moeten duidelijk worden aangekondigd, gekoppeld aan de relevante velden en precieze instructies geven voor correctie.
Veldgroepen met aria-labelledby
Slecht voorbeeld
<!-- ❌ Slecht: Ongeorganiseerde structuur -->
<form>
<h4>Voorkeuren</h4>
<!-- Gemengde elementen zonder logica -->
<div>
<input type="radio" name="contact-bad"
id="email-contact-bad" value="email">
<label for="email-contact-bad">E-mail</label>
<input type="checkbox" id="newsletter-bad"
value="newsletter">
<label for="newsletter-bad">Nieuwsbrief</label>
</div>
<div>
<input type="radio" name="contact-bad"
id="phone-contact-bad" value="phone">
<label for="phone-contact-bad">Telefoon</label>
<!-- Tekstveld zonder context -->
<input type="text" placeholder="Naam">
</div>
<div>
<input type="checkbox" id="offers-bad" value="offers">
<label for="offers-bad">Aanbiedingen</label>
<input type="radio" name="contact-bad"
id="sms-contact-bad" value="sms">
<label for="sms-contact-bad">SMS</label>
</div>
<!-- Selecteer zonder context -->
<select>
<option>Kies...</option>
<option>Dagelijks</option>
<option>Wekelijks</option>
</select>
<button type="submit">OK</button>
</form>Goed voorbeeld
<!-- ✅ Goed: Logische groepen met aria-labelledby -->
<form>
<h4>Contactvoorkeuren</h4>
<!-- Persoonlijke informatie sectie -->
<div role="group" aria-labelledby="personal-info-title">
<h5 id="personal-info-title">
Persoonlijke informatie
</h5>
<label for="name-good">Volledige naam *</label>
<input
type="text"
id="name-good"
name="name"
required
aria-required="true"
aria-describedby="name-desc">
<span id="name-desc">
Je volledige naam zoals deze in onze communicatie verschijnt
</span>
</div>
<!-- Radioknoppen groep -->
<div
role="radiogroup"
aria-labelledby="contact-method-title"
aria-required="true">
<h5 id="contact-method-title">
Voorkeursmethode voor contact *
</h5>
<input
type="radio"
name="contact-good"
id="email-contact-good"
value="email"
required>
<label for="email-contact-good">E-mail</label>
<input
type="radio"
name="contact-good"
id="phone-contact-good"
value="phone"
required>
<label for="phone-contact-good">Telefoon</label>
</div>
<!-- Selectievakjes groep -->
<div role="group" aria-labelledby="comm-types-title">
<h5 id="comm-types-title">Communicatietypes</h5>
<p id="comm-types-desc">
Selecteer de types communicatie die je wenst te ontvangen
</p>
<input
type="checkbox"
id="newsletter-good"
value="newsletter"
aria-describedby="comm-types-desc">
<label for="newsletter-good">Nieuwsbrief</label>
</div>
<button type="submit">
Voorkeuren opslaan
</button>
</form>Uitleg: Groepen gerelateerde velden moeten worden gekoppeld aan hun titels via aria-labelledby om duidelijke semantische relaties te creëren, vooral voor radioknoppen en selectievakjes.
Complex formulier met realtime validatie
Slecht voorbeeld
<!-- ❌ Slecht: Opdringerige validatie -->
<form>
<h4>Account aanmaken</h4>
<!-- Onmiddellijke validatie bij elke toetsaanslag -->
<label>Gebruikersnaam</label>
<input
type="text"
v-model="complexBadForm.username"
@input="validateUsernameBad"
:class="{ 'error': usernameBadError }">
<!-- Fout die constant verschijnt/verdwijnt -->
<div v-if="usernameBadError">
</div>
<!-- Geen koppeling met aria-describedby -->
<label>E-mail</label>
<input
type="email"
v-model="complexBadForm.email"
@input="validateEmailBad"
:class="{ 'error': emailBadError }">
<div v-if="emailBadError"></div>
<!-- Foutmeldingen te kort en niet nuttig -->
<!-- Geen autocomplete om wachtwoordbeheerders te helpen -->
<label>Wachtwoord</label>
<input
type="password"
v-model="complexBadForm.password"
@input="validatePasswordBad"
:class="{ 'error': passwordBadError }">
<div v-if="passwordBadError"></div>
<button type="submit">Account aanmaken</button>
</form>Goed voorbeeld
<!-- ✅ Goed: Respectvolle validatie met feedback -->
<form>
<h4>Account aanmaken</h4>
<!-- Validatie bij blur, niet bij input -->
<label for="username-complex">Gebruikersnaam *</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>
<!-- Duidelijke instructies -->
<div id="username-requirements">
<h6>Vereisten:</h6>
<ul>
<li :class="{ valid: complexGoodForm.username.length >= 3 }">
Minimaal 3 tekens
</li>
<li :class="{ valid: /^[a-zA-Z0-9_]+$/.test(username) }">
Alleen letters, cijfers en underscore
</li>
</ul>
</div>
<!-- Toegankelijke feedback -->
<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">
✅ Gebruikersnaam beschikbaar
</div>
<!-- Wachtwoordsterkte-indicator -->
<label for="password-complex">Wachtwoord *</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>Wachtwoordsterkte:</h6>
<div class="strength-indicator">
<div
class="strength-bar"
:class="`strength-${passwordStrength}`"
:style="{ width: `${passwordStrength * 25}%` }">
</div>
</div>
</div>
<button type="submit" :disabled="!isFormValid">
Account aanmaken
</button>
</form>Uitleg: Voor complexe formulieren kan realtime validatie helpen, maar het moet op een niet-opdringerige en toegankelijke manier worden geïmplementeerd.