Een toegankelijk dialoogvenster

Wil je een toegankelijk dialoogvenster maken? In dit artikel leg ik uit welke stappen je moet nemen volgens WCAG 2.2.

Als ontwikkelaar wil je dialoogvensters maken die iedereen kan gebruiken. Met de juiste aria-attributen en door kleine aanpassingen in de toetsenbordbediening toe te passen, zorg je voor een logisch en toegankelijk dialoogvenster.

Wat is een dialoogvenster?

Een dialoogvenster is een interface-element dat bovenop de hoofdinhoud verschijnt. Het vraagt de aandacht van de gebruiker en vereist een actie voordat je verder kan.

Structuur opbouwen

Het openen van een dialoogvenster gaat met een knop:

<button id="open-dialog">Open dialoogvenster</button>

Gebruik een <div>-element met role='dialog' als container voor het dialoogvenster:

<div 
    role="dialog" 
    aria-modal="true" 
    aria-labelledby="dialog-title" 
    aria-describedby="dialog-description" 
    class="modal" 
    id="demo-dialog">

In deze code zie je verschillende ARIA-attributen. Ik leg ze even uit:

  • role="dialog": Vertelt hulptechnologieën dat dit een dialoogvenster is.
  • aria-modal="true": Geeft aan dat de rest van de pagina niet kan worden gebruikt als dit venster open is.
  • aria-labelledby="dialog-title": Koppelt de titel aan het dialoogvenster.
  • aria-describedby="dialog-description": Koppelt de beschrijving aan het dialoogvenster.

In de container staat de inhoud van het dialoogvenster. De ARIA-attributen van de container verwijzen naar het ID van elementen van deze inhoud:

<div class="modal-content">

  <!-- Sluiten knop -->
  <button class="close-button" aria-label="Sluiten">×</button>

  <!-- Titel -->
  <h2 id="dialog-title">Titel van dialoogvenster</h2>

  <!-- Inhoud -->
  <p id="dialog-description">Beschrijving van het doel van dit venster.</p>

  <!-- Actie knoppen  -->
  <div class="modal-actions">
    <button class="secondary-button">Annuleren</button>
    <button class="primary-button">Bevestigen</button>
  </div>

</div>

Interactie

Gebruik JavaScript om het dialoogvenster goed te laten werken:

Focusmanagement

Bij het openen van het dialoogvenster moet de focus automatisch naar een element binnen het venster gaan. Bij het sluiten keert de focus terug naar het element waarmee het venster werd geopend.

// Bij openen
openButton.addEventListener('click', function() {
  openModal();
  closeButton.focus(); // Focus op eerste element in het dialoogvenster
});

// Bij sluiten
function closeModal() {
  modal.style.display = 'none';
  openButton.focus(); // Focus terug naar knop die het dialoogvenster opende
}

Focus vasthouden

Gebruikers mogen niet buiten het dialoogvenster kunnen navigeren:

// Focus binnen het dialoogvenster houden
modal.addEventListener('keydown', function(event) {
  // ESC-toets sluit het venster
  if (event.key === 'Escape') {
    closeModal();
  }
  
  // TAB-toets binnen het venster houden
  if (event.key === 'Tab') {
    // Alle focusbare elementen vinden
    const focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];
    
    // Als we achteruit tabben en bij het eerste element zijn
    if (event.shiftKey && document.activeElement === firstElement) {
      lastElement.focus();
      event.preventDefault();
    }
    // Als we vooruit tabben en bij het laatste element zijn
    else if (!event.shiftKey && document.activeElement === lastElement) {
      firstElement.focus();
      event.preventDefault();
    }
  }
});

Escape-toets

Gebruikers het dialoogvenster laten sluiten met de Escape-toets:

document.addEventListener('keydown', function(event) {
  if (event.key === 'Escape' && modal.style.display === 'block') {
    closeModal();
  }
});

Codevoorbeeld

HTML
<div 
    role="dialog" 
    aria-modal="true" 
    aria-labelledby="dialog-title" 
    aria-describedby="dialog-description" 
    class="modal" 
    id="demo-dialog">
  <div class="modal-content">

    <!-- Sluiten knop -->
    <button class="close-button" aria-label="Sluiten">×</button>

    <!-- Titel -->
    <h2 id="dialog-title">Titel van dialoogvenster</h2>

    <!-- Inhoud -->
    <p id="dialog-description">Beschrijving van het doel van dit venster.</p>

    <!-- Actie knoppen  -->
    <div class="modal-actions">
      <button class="secondary-button">Annuleren</button>
      <button class="primary-button">Bevestigen</button>
    </div>

  </div>
</div>
Javascript
// Elementen selecteren
const modal = document.querySelector('.modal');
const openButton = document.querySelector('.open-modal-button');
const closeButton = document.querySelector('.close-button');
const cancelButton = document.querySelector('.secondary-button');
const confirmButton = document.querySelector('.primary-button');

// Functie om het dialoogvenster te openen
function openModal() {
  modal.style.display = 'block';
  // Focus op de eerste knop in het venster
  closeButton.focus();
  // Bewaar de laatst gefocuste element voor als we sluiten
  lastFocus = document.activeElement;
}

// Functie om het dialoogvenster te sluiten
function closeModal() {
  modal.style.display = 'none';
  // Terug naar waar de gebruiker was
  lastFocus.focus();
}

// Event listeners
openButton.addEventListener('click', openModal);
closeButton.addEventListener('click', closeModal);
cancelButton.addEventListener('click', closeModal);
confirmButton.addEventListener('click', function() {
  // Voer actie uit en sluit venster
  closeModal();
});

// Focus binnen het dialoogvenster houden (focus trap)
modal.addEventListener('keydown', function(event) {
  // ESC-toets sluit het venster
  if (event.key === 'Escape') {
    closeModal();
  }
  
  // TAB-toets binnen het venster houden
  if (event.key === 'Tab') {
    // Alle focusbare elementen vinden
    const focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];
    
    // Als we achteruit tabben en bij het eerste element zijn
    if (event.shiftKey && document.activeElement === firstElement) {
      lastElement.focus();
      event.preventDefault();
    }
    // Als we vooruit tabben en bij het laatste element zijn
    else if (!event.shiftKey && document.activeElement === lastElement) {
      firstElement.focus();
      event.preventDefault();
    }
  }
});

Demo

Toegankelijk Dialoogvenster

Toegankelijkheidskenmerken

  • Toetsenbordbediening
    • Focus wordt vastgehouden binnen het dialoogvenster.
    • Focus keert terug naar het element dat focus had vóór het openen.
    • Sluit het dialoogvenster met de Escape-toets.
  • Schermlezers
    • Dialoogvenster heeft een duidelijke label en beschrijving.

Conclusie

Het dialoogvenster voldoet zo aan de volgende succescriteria:

  • 1.3.1 Info en relaties
  • 2.1.1 Toetsenbord
  • 2.1.2 Geen toetsenbordval
  • 2.1.4 Sneltoetsen
  • 2.4.3 Focus volgorde
  • 2.4.6 Koppen en labels
  • 2.4.7 Focus zichtbaar
  • 4.1.2 Naam, rol, waarde

Let bij het toepassen van het dialoogvenster ook op de juiste kleuren voor:

  • 1.4.3 Contrast (minimum)
  • 1.4.11 Contrast van niet-tekstuele content

Wil je zeker weten dat jouw dialoogvenster toegankelijk is? Test deze dan zelf eens met een schermlezer en alleen met het toetsenbord.

Heb je vragen over deze implementatie? Laat het me weten!