Als je een website gebruikt zonder dat je het scherm kunt zien. Hoe weet je dan:
- Welke tekst een kop is en welk niveau deze heeft?
- Welke items bij elkaar horen in een lijst?
- Welke cellen bij welke rijen of kolommen horen in een tabel?
Als je alleen visuele opmaak gebruikt (zoals vetgedrukte tekst of grotere lettertypen) zonder semantische HTML, dan mist een gebruiker van een schermlezer deze structuur.
Wat is WCAG 1.3.1 eigenlijk?
Succescriterium 1.3.1 Info en relaties is een van de belangrijke WCAG-succescriteria. Het criterium stelt:
Informatie, structuur en relaties overgebracht door presentatie kunnen door software bepaald worden of zijn beschikbaar in tekst.
Simpel gezegd: Alle informatie, structuur en relaties die iemand visueel kan waarnemen, moeten ook in de code worden vastgelegd. Hierdoor zijn deze ook beschikbaar voor mensen die hulptechnologieën gebruiken, zoals een schermlezer. En dat is precies waar semantische HTML in beeld komt.
Semantische HTML is de belangrijkste manier om aan dit criterium te voldoen, maar het gaat iets breder. Ook informatie die door andere visuele middelen wordt overgebracht moet programmatisch bepaalbaar zijn.
Waarom semantische HTML zo belangrijk is
Semantische HTML gebruikt elementen in de code die betekenis geven aan je content, in plaats van alleen te focussen op hoe het eruit ziet.
Vergelijk deze twee voorbeelden:
<!-- Niet-semantisch -->
<div class="title">Mijn hoofdkop</div>
<div class="subtitle">Mijn subkop</div>
<!-- Semantisch -->
<h1>Mijn hoofdkop</h1>
<h2>Mijn subkop</h2>
Voor iemand die goed kan zien is er misschien geen verschil als je de juiste CSS toepast, maar voor een schermlezer is er wél verschil. In het semantische voorbeeld weet hulptechnologie dat het met koppen te maken heeft en wat hun onderlinge hiërarchie is.
Koppen
Laten we beginnen met koppen. Deze zijn ontzettend belangrijk voor de structuur van je pagina.
Waarom goede koppen belangrijk zijn
Koppen zorgen voor:
- Context voor de inhoud die volgt
- Navigatiepunten voor gebruikers van een schermlezer
Wist je dat mensen die een schermlezer gebruiken vaak navigeren door de koppen op een pagina? Door op één toets te drukken kunnen ze van kop naar kop springen. Als je kopstructuur niet klopt, dan is dat alsof je de inhoudsopgave van een boek helemaal door elkaar haalt.
In de Rotor van VoiceOver ziet dit er zo uit:

Best practices voor koppen
Vermijd zo veel mogelijk het overslaan van kopniveaus. Ga van h1 naar h2 naar h3 en spring dus niet van h1 naar h3.
<!-- Slecht voorbeeld -->
<h1>Productoverzicht</h1>
<h3>Elektronica</h3> <!-- Sprong van h1 naar h3 -->
<h6>Smartphones</h6> <!-- Sprong van h3 naar h6 -->
<!-- Goed voorbeeld -->
<h1>Productoverzicht</h1>
<h2>Elektronica</h2>
<h3>Smartphones</h3>
<h3>Laptops</h3>
<h2>Kleding</h2>
<h3>Dames</h3>
<h3>Heren</h3>
Begin de content van elke pagina met een h1 en zorg dat deze kop de inhoud van de pagina beschrijft.
Veelgemaakte fouten
- Tekst die een kop is, wordt in de code niet opgemaakt met een kop-element
- Kop-elementen worden alleen maar gebruikt omdat ze er “mooi” uitzien (voor het ontwerp of de stijl)
- Kopniveaus worden overgeslagen, waardoor de betekenis van de content verloren gaat
<!-- Slecht voorbeeld -->
<div class="heading">Belangrijke sectie</div>
<!-- Goed voorbeeld -->
<h2>Belangrijke sectie</h2>
Lijsten
Lijsten maken je tekst makkelijker te lezen en te begrijpen.
Soorten lijsten
Er zijn drie soorten lijsten om gerelateerde items te groeperen en hiërarchie aan te brengen:
- Ongeordende lijst of opsomming
- Geordende of genummerde lijst
- Definitielijst
<!-- Ongeordende lijst -->
<ul>
<li>Appels</li>
<li>Bananen</li>
<li>Peren</li>
</ul>
<!-- Geordende lijst -->
<ol>
<li>Meng de ingrediënten</li>
<li>Bak het deeg</li>
<li>Laat het afkoelen</li>
</ol>
<!-- Definitielijst -->
<dl>
<dt>HTML</dt>
<dd>HyperText Markup Language, de standaardtaal voor webpagina's</dd>
<dt>CSS</dt>
<dd>Cascading Style Sheets, voor de opmaak van webpagina's</dd>
</dl>
Schermlezers kondigen het type lijst aan en ook het aantal items in de lijst. Hierdoor weet de gebruiker wat te verwachten.
Veelgemaakte fouten
- Tekst wordt geplaatst met handmatige opsommingstekens of nummering
- Geneste lijsten worden niet juist gestructureerd
<!-- Slecht voorbeeld -->
<div>- Eerste item</div>
<div>- Tweede item</div>
<div>- Derde item</div>
<!-- Goed voorbeeld -->
<ul>
<li>Eerste item</li>
<li>Tweede item</li>
<li>Derde item</li>
</ul>
Tabellen
Tabellen gebruik je om gegevens te presenteren of te vergelijken. Tabellen worden vaak verkeerd gebruikt. Ze zijn echt bedoeld voor tabulaire gegevens, en dus niet voor layout.
Een toegankelijke tabel maken
Een toegankelijke tabel heeft altijd:
<th>: Tabelkoppen (metscope-attribuut)<td>: Tabeldata (cellen)<caption>: Beschrijft waar de tabel over gaat<thead>,<tbody>,<tfoot>: Structureren de tabel in logische secties
<table>
<caption>Aantal succescriteria per WCAG niveau</caption>
<thead>
<tr>
<th scope="col">WCAG versie</th>
<th scope="col">Niveau A</th>
<th scope="col">Niveau AA</th>
<th scope="col">Niveau AAA</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">WCAG 2.1</th>
<td>30</td>
<td>50</td>
<td>78</td>
</tr>
<tr>
<th scope="row">WCAG 2.2</th>
<td>30</td>
<td>54</td>
<td>86</td>
</tr>
</tbody>
</table>
Het scope-attribuut is belangrijk. Het vertelt hulptechnologie of een tabelkop bij een rij of kolom hoort. Gebruik dit attribuut altijd als er koppen in twee richtingen zijn. Gebruik de waarde col voor kolom en row voor rij.
Bij eenvoudige tabellen met koppen alleen in de eerste rij bepalen moderne schermlezers vaak zelf dat de koppen bij de kolommen eronder horen.
Complexe tabellen
Voor complexere tabellen kun je headers en id gebruiken:
<table>
<caption>Kwartaalcijfers per afdeling</caption>
<tr>
<td></td>
<th id="q1">Kwartaal 1</th>
<th id="q2">Kwartaal 2</th>
<th id="q3">Kwartaal 3</th>
<th id="q4">Kwartaal 4</th>
</tr>
<tr>
<th id="sales">Verkoop</th>
<td headers="sales q1">€10K</td>
<td headers="sales q2">€12K</td>
<td headers="sales q3">€15K</td>
<td headers="sales q4">€18K</td>
</tr>
<tr>
<th id="marketing">Marketing</th>
<td headers="marketing q1">€5K</td>
<td headers="marketing q2">€6K</td>
<td headers="marketing q3">€7K</td>
<td headers="marketing q4">€8K</td>
</tr>
</table>
Door de headers-attributen kan een schermlezer hier aankondigen: “Verkoop Kwartaal 1 €10K”. Zo weet de gebruiker precies welke gegevens bij welke koppen horen.
Veelgemaakte fouten
- Tabellen worden gebruikt voor de opmaak van een pagina
- Datatabellen zonder tabelkoppen
<!-- Slecht voorbeeld -->
<table>
<tbody>
<tr>
<th scope="row">Januari</th>
<td>€750</td>
<td>€300</td>
<td>€1050</td>
</tr>
<tr>
<th scope="row">Februari</th>
<td>€750</td>
<td>€280</td>
<td>€1030</td>
</tr>
</tbody>
</table>
<!-- Goed voorbeeld -->
<table>
<caption>Maandelijkse uitgaven</caption>
<thead>
<tr>
<th scope="col">Maand</th>
<th scope="col">Huur</th>
<th scope="col">Eten</th>
<th scope="col">Totaal</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Januari</th>
<td>€750</td>
<td>€300</td>
<td>€1050</td>
</tr>
<tr>
<th scope="row">Februari</th>
<td>€750</td>
<td>€280</td>
<td>€1030</td>
</tr>
</tbody>
</table>
Formulieren
Labels
Elk invoerveld moet een programmatisch gekoppeld label hebben. Dit doe je met het for-attribuut dat verwijst naar het id van het invoerveld, of door het invoerveld binnen het <label>-element te plaatsen.
<!-- for en id -->
<label for="email">E-mailadres</label>
<input type="email" id="email" name="email">
<!-- Nesting -->
<label>
E-mailadres
<input type="email" name="email">
</label>
Een schermlezer leest bij focus op het invoerveld automatisch het gekoppelde label voor. Zonder die koppeling weet de gebruiker niet welke informatie wordt verwacht.
Groepen van invoervelden
Groepeer gerelateerde invoervelden met <fieldset> en <legend>. Dit is vooral belangrijk bij keuzerondjes en selectievakjes die samen één vraag beantwoorden.
<fieldset>
<legend>
Bezorgvoorkeuren
</legend>
<label>
<input type="radio" name="bezorging" value="thuis">
Thuisbezorgen
</label>
<label>
<input type="radio" name="bezorging" value="ophalen">
Ophalen in winkel
</label>
</fieldset>
De schermlezer kondigt eerst de <legend> aan (“Bezorgvoorkeuren”) en vervolgens elk individueel label. Zo begrijpt de gebruiker de context.
Veelvoorkomende fouten
- Labels die visueel naast het veld staan maar niet programmatisch gekoppeld zijn.
- Groepen selectievakjes zonder
<fieldset>.
<!-- Slecht voorbeeld -->
<span>
E-mailadres
</span>
<input type="email" name="email">
<!-- Goed voorbeeld -->
<label for="email">
E-mailadres
</label>
<input type="email" id="email" name="email">
<!-- Slecht voorbeeld -->
<p>
Hoe wil je betalen?
</p>
<input type="radio" name="betaling" value="ideal">
iDEAL
<input type="radio" name="betaling" value="creditcard">
Creditcard
<!-- Goed voorbeeld -->
<fieldset>
<legend>
Hoe wil je betalen?
</legend>
<label>
<input type="radio" name="betaling" value="ideal">
iDEAL
</label>
<label>
<input type="radio" name="betaling" value="creditcard">
Creditcard
</label>
</fieldset>
Oriëntatiepunten (landmarks)
De indeling van je pagina zelf óók onderdeel van 1.3.1. Schermlezers gebruiken deze om snel te navigeren.
De belangrijkste paginagebieden zijn:
<header>: de paginakop, met logo en navigatie<nav>: een navigatieblok<main>: de hoofdinhoud van de pagina<aside>: aanvullende informatie, niet essentieel voor de hoofdinhoud<footer>: de paginavoet
<header>
<a href="/">Logo</a>
<nav aria-label="Hoofdnavigatie">
<ul>
<li>
<a href="/over">Over ons</a>
</li>
</ul>
</nav>
</header>
<main>
<h1>
Productoverzicht
</h1>
...
</main>
<footer>
<p>
Copyright
</p>
</footer>
Gebruik je meerdere <nav>-elementen op een pagina, geef ze dan elk een toegankelijke naam om ze van elkaar te onderscheiden.
Veelgemaakte fouten
- De hoofdinhoud staat niet in een
<main>-element - Navigatieblokken worden niet opgemaakt als
<nav> - Er zijn meerdere
<nav>-elementen zonder onderscheidende naam
Praktische tips voor het testen
Om je implementatie te controleren:
- Schakel je CSS uit: Is je content nog steeds logisch en begrijpelijk?
- Controleer je kopstructuur: Beschrijft het op hoofdlijnen de inhoud van de pagina?
- Gebruik zelf eens een schermlezer: NVDA (Windows) of VoiceOver (Mac) zijn goede opties
- Gebruik de Web Developer Toolbar: Om HTML-elementen te markeren
Wat je concreet moet doen
Voor redacteuren
Je hoeft geen code te schrijven. Maar je kunt wél veel doen om de structuur van je content goed te houden.
- Gebruik de opmaakstijlen in je editor. Maak een kop niet vetgedrukt, maar kies het juiste kopniveau (Kop 2, Kop 3, enzovoorts). Zo krijgt je kop de juiste HTML-code.
- Maak een lijst met de lijstknop. Typ geen streepjes of nummers handmatig. Gebruik de knop voor een opsomming of genummerde lijst.
- Maak een tabel met de tabelknop. Kopieer geen tabel vanuit Word of Excel zonder te controleren of de tabelkoppen kloppen.
- Koppel altijd een label aan een formulierveld. Gebruik je een formulierbouwer? Controleer dan of elk veld een zichtbaar label heeft.
Voor ontwikkelaars
Jij bepaalt welke HTML-elementen er in de code staan. Gebruik altijd het element dat de betekenis van je content het beste beschrijft.
- Kies het juiste element. Gebruik een
<h2>voor een kop, een<ul>voor een opsomming en een<table>voor tabulaire gegevens. Gebruik geen<div>of<span>waar een semantisch element beschikbaar is. - Bouw tabellen met koppen. Gebruik
<th>met eenscope-attribuut. Voeg een<caption>toe die beschrijft waar de tabel over gaat. - Koppel labels aan invoervelden. Gebruik het
for-attribuut of plaats het invoerveld in het<label>-element. Groepeer keuzerondjes en selectievakjes met<fieldset>en<legend>. - Structureer je pagina met oriëntatiepunten. Gebruik
<header>,<nav>,<main>en<footer>. Geef meerdere<nav>-elementen een onderscheidendearia-label.
Conclusie
Semantische HTML is geen extra werk maar eigenlijk de enige juiste manier van ontwikkelen.
Heb je vragen over specifieke implementaties? Laat het me weten!