Toegankelijkheid heeft een imagoprobleem. Het wordt gezien als extra werk, een set regels die je na het bouwen moet afvinken, iets voor grote overheidsprojecten.
Na jaren van bouwen aan platformen voor miljoenen gebruikers — en het bouwen van AllyScan, een tool specifiek voor toegankelijkheidstesting — ben ik tot een andere conclusie gekomen: WCAG-compliance is gewoon goede code schrijven.
Waarom het ertoe doet
In Nederland heeft ongeveer 1 op de 6 mensen een functiebeperking die invloed heeft op hoe ze het web gebruiken. Visuele beperkingen, motorische beperkingen, dyslexie, epilepsie — het spectrum is breed.
Maar ook zonder beperking profiteer je van toegankelijke code:
- Betere SEO: Semantische HTML en beschrijvende alt-teksten helpen zoekmachines
- Verbeterde keyboard navigatie: Prettig voor power users, noodzakelijk voor anderen
- Robustere code: Toegankelijke componenten zijn doorgaans beter doordacht
Wist je dat?
Toegankelijke code is betere code. Semantische HTML verbetert SEO, keyboard navigatie helpt power users, en robuuste componenten zijn beter onderhoudbaar.
1. Audit uitvoeren
Scan je site met axe DevTools of Lighthouse. Identificeer de grootste problemen.
2. Quick wins fixen
Alt-teksten, kleurcontrast, focus states en semantische HTML. Vaak 60% van de issues.
3. Keyboard test
Navigeer je hele site met alleen het toetsenbord. Tab, Enter, Space, Escape.
4. Automatiseren
Voeg jest-axe en AllyScan toe aan je CI/CD pipeline. Maak het onmogelijk om regressies te mergen.
De patronen die ik overal gebruik
1. Semantische HTML als fundament
Voor accessibility geldt: gebruik het juiste element voor de juiste taak. Een <button> voor acties, een <a> voor navigatie, <nav> voor navigatieblokken.
// ❌ Niet dit
<div onClick={handleSubmit} className="btn">
Versturen
</div>
// ✅ Maar dit
<button type="submit" onClick={handleSubmit}>
Versturen
</button>Het verschil: de button is automatisch focussable, reageert op Enter/Space, en schermlezer kondigt hem aan als button.
Ontoegankelijk component
- ❌<div onClick={...}> als button
- ❌Geen focus state zichtbaar
- ❌Kleurcontrast ratio 2.5:1
- ❌Geen ARIA labels
- ❌Niet bereikbaar met keyboard
Toegankelijk component
- ✅<button> met semantische HTML
- ✅Duidelijke focus ring met outline
- ✅Kleurcontrast ratio 7:1 (AAA)
- ✅aria-label en aria-pressed
- ✅Tab, Enter en Space support
2. Focus management
Modale dialogen, drawers en dynamische content vereisen bewust focus management:
export function Modal({ isOpen, onClose, children }: ModalProps) {
const firstFocusableRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (isOpen) {
// Focus het eerste interactieve element bij openen
firstFocusableRef.current?.focus();
}
}, [isOpen]);
return isOpen ? (
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
// Voorkom focus buiten de modal
onKeyDown={(e) => e.key === 'Escape' && onClose()}
>
<button ref={firstFocusableRef} onClick={onClose} aria-label="Sluit modal">
×
</button>
<h2 id="modal-title">{title}</h2>
{children}
</div>
) : null;
}3. Kleurcontrast automatisch checken
Handmatig kleurcontrast controleren is tijdrovend en foutgevoelig. Ik gebruik een combinatie van:
- Storybook met
@storybook/addon-a11yvoor visuele controle per component - axe-core in Jest tests voor geautomatiseerde checks
- AllyScan voor productiesites (mijn eigen tool)
// Toegankelijkheidstest in Jest
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
it('Button has no accessibility violations', async () => {
const { container } = render(<Button>Versturen</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});4. Alt-teksten die betekenis toevoegen
// ❌ Nutteloos
<img src="leroy.jpg" alt="afbeelding" />
// ❌ Te beschrijvend
<img src="leroy.jpg" alt="Een foto van een man met bruin haar die naar de camera kijkt" />
// ✅ Context-gedreven
<img src="leroy.jpg" alt="Leroy Steding, Senior Full-Stack Developer" />
// ✅ Decoratief? Dan leeg laten.
<img src="decorative-wave.svg" alt="" role="presentation" />5. ARIA alleen als nodig
Een veelgemaakte fout is ARIA overal toevoegen. De eerste regel van ARIA gebruik: gebruik geen ARIA als je een native HTML element kunt gebruiken.
// ❌ Overbodig
<button aria-role="button">Klik hier</button>
// ✅ Nuttig: status melden aan schermlezer
<button aria-pressed={isActive} onClick={toggle}>
{isActive ? 'Aan' : 'Uit'}
</button>AllyScan: toegankelijkheid automatiseren
Dit is precies waarom ik AllyScan heb gebouwd. Een CI/CD pipeline die na elke deployment automatisch je volledige site doorscant op WCAG 2.1 AA/AAA overtredingen, met een Chrome-extensie voor realtime feedback tijdens het ontwikkelen.
Het principe: maak het onmogelijk om ontoegankelijke code te mergen.
# .github/workflows/accessibility.yml
- name: Run AllyScan
run: npx allyscan --url ${{ env.DEPLOY_URL }} --standard WCAG21AA
env:
ALLYSCAN_TOKEN: ${{ secrets.ALLYSCAN_TOKEN }}Hoe je ermee start
- Installeer de axe DevTools browser extensie — gratis, geeft direct feedback
- Navigeer je site met alleen het toetsenbord — Tab, Shift+Tab, Enter, Space, pijltoetsen. Is alles bereikbaar?
- Zet een schermlezer aan — NVDA (Windows, gratis), VoiceOver (Mac/iOS, ingebouwd), TalkBack (Android)
- Voeg
jest-axetoe aan je bestaande test suite
Toegankelijkheid hoeft niet perfect te zijn op dag één. Maar het moet wel structureel verbeteren. Begin klein, maak het onderdeel van je definition of done, en bouw het in je tooling.
Je gebruikers — alle gebruikers — verdienen het.