2.1.2 - Billentyűzetcsapda nélkül

Röviden a szabványpontról

A WCAG 2.1.2 (No Keyboard Trap) előírja, hogy amikor a billentyűzet fókusz egy oldal komponensére kerül billentyűzetes interfész használatával, a felhasználónak képesnek kell lennie a fókusz elmozgatására arról a komponensről csak billentyűzet használatával. Ez minden olyan webes tartalomra vonatkozik, amely billentyűzettel navigálható vagy kezelhető.

Cél: Biztosítani, hogy a kizárólag billentyűzetre támaszkodó felhasználók szabadon navigálhassanak anélkül, hogy elakadnának, megelőzve a frusztrációt és az irányítás elvesztését.

Kiket érint

Elsődleges felhasználók: Mozgássérült emberek, akik nem tudnak egeret használni és teljes mértékben billentyűzetes navigációra vagy billentyűzetet emuláló alternatív beviteli eszközökre támaszkodnak.

Másodlagos előnyök: Javítja az általános billentyűzetes navigációs élményt minden felhasználó számára, beleértve a haladó felhasználókat és a kisegítő technológiákat használókat, mint képernyőolvasók vagy hangvezérlés.

Tesztelés

  1. Csak billentyűzetes navigációs teszt: Használd a Tab billentyűt (és Shift+Tab) a fókusz mozgatásához az oldal összes interaktív elemén keresztül. Ellenőrizd, hogy minden komponensből ki tudsz lépni egér használata nélkül
  2. Csapda észlelés: Amikor a fókusz egy widgeten belül van (pl. modális dialógus, egyedi legördülő), próbáld kimozgatni a fókuszt gyakori billentyűk használatával, mint Tab, Shift+Tab vagy Escape. A fókusz nem lehet csapdában
  3. Kisegítő technológiák tesztelése: Teszteld a billentyűzetes navigációt képernyőolvasókkal és hangvezérléssel, hogy biztosítsd, hogy nem történnek csapdák
  4. Automatizált tesztelés: Használj eszközöket, mint az axe DevTools a 2.1.2 szabály által jelzett billentyűzet csapda problémák észlelésére
  5. Manuális kód áttekintés: Vizsgáld meg a billentyűzet fókuszt kezelő JavaScript eseménykezelőket, hogy biztosítsd, hogy nem blokkolják a fókusz elmozgatását

Jó gyakorlatok

1. Modális dialógusok megfelelő kezelése

<div class="modal-example">
  <button id="open-modal-btn" class="open-button">Modál megnyitása</button>
  
  <div role="dialog" aria-modal="true" aria-labelledby="modal-title" id="accessible-modal" class="modal" hidden>
    <div class="modal-content">
      <h2 id="modal-title">Hozzáférhető modál</h2>
      <p>Ez egy megfelelően implementált modál, amelyből könnyen ki lehet lépni billentyűzettel.</p>
      
      <div class="modal-form">
        <label for="modal-input">Példa mező:</label>
        <input type="text" id="modal-input" placeholder="Írd be a szöveget">
      </div>
      
      <div class="modal-actions">
        <button id="save-btn" class="primary-btn">Mentés</button>
        <button id="close-modal-btn" class="secondary-btn">Bezárás</button>
      </div>
    </div>
  </div>
</div>

<style>
.modal-example {
  margin: 20px;
}

.open-button {
  background-color: #007bff;
  color: white;
  border: none;
  padding: 12px 24px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

.open-button:hover, .open-button:focus {
  background-color: #0056b3;
  outline: 2px solid #80bdff;
  outline-offset: 2px;
}

.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal[hidden] {
  display: none;
}

.modal-content {
  background-color: white;
  padding: 30px;
  border-radius: 8px;
  max-width: 500px;
  width: 90%;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  position: relative;
}

.modal-form {
  margin: 20px 0;
}

.modal-form label {
  display: block;
  margin-bottom: 8px;
  font-weight: bold;
}

.modal-form input {
  width: 100%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
  font-size: 16px;
}

.modal-form input:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  border-color: #007bff;
}

.modal-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 20px;
}

.primary-btn {
  background-color: #28a745;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
}

.secondary-btn {
  background-color: #6c757d;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
}

.primary-btn:hover, .primary-btn:focus,
.secondary-btn:hover, .secondary-btn:focus {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

.primary-btn:hover { background-color: #218838; }
.secondary-btn:hover { background-color: #545b62; }
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const openModalBtn = document.getElementById('open-modal-btn');
  const modal = document.getElementById('accessible-modal');
  const closeModalBtn = document.getElementById('close-modal-btn');
  const saveBtn = document.getElementById('save-btn');
  const modalInput = document.getElementById('modal-input');
  
  let lastFocusedElement;
  let modalFocusableElements;
  let firstFocusableElement;
  let lastFocusableElement;

  function getFocusableElements() {
    modalFocusableElements = modal.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    firstFocusableElement = modalFocusableElements[0];
    lastFocusableElement = modalFocusableElements[modalFocusableElements.length - 1];
  }

  function openModal() {
    // Aktuális fókusz mentése
    lastFocusedElement = document.activeElement;
    
    // Modál megjelenítése
    modal.hidden = false;
    
    // Fókuszálható elemek frissítése
    getFocusableElements();
    
    // Fókusz az első elemre (input mező)
    modalInput.focus();
    
    // Eseménykezelő hozzáadása a fókusz csapdázásához
    modal.addEventListener('keydown', handleModalKeydown);
  }

  function closeModal() {
    // Modál elrejtése
    modal.hidden = true;
    
    // Eseménykezelő eltávolítása
    modal.removeEventListener('keydown', handleModalKeydown);
    
    // Fókusz visszaadása az eredeti elemnek
    if (lastFocusedElement) {
      lastFocusedElement.focus();
    }
  }

  function handleModalKeydown(e) {
    // Escape billentyűvel bezárás
    if (e.key === 'Escape') {
      e.preventDefault();
      closeModal();
      return;
    }

    // Tab billentyű kezelése a fókusz csapdázásához
    if (e.key === 'Tab') {
      if (e.shiftKey) {
        // Shift + Tab: hátrafelé
        if (document.activeElement === firstFocusableElement) {
          e.preventDefault();
          lastFocusableElement.focus();
        }
      } else {
        // Tab: előre
        if (document.activeElement === lastFocusableElement) {
          e.preventDefault();
          firstFocusableElement.focus();
        }
      }
    }
  }

  // Esemény figyelők
  openModalBtn.addEventListener('click', openModal);
  closeModalBtn.addEventListener('click', closeModal);
  
  saveBtn.addEventListener('click', function() {
    alert('Adatok mentve!');
    closeModal();
  });

  // Háttérre kattintás
  modal.addEventListener('click', function(e) {
    if (e.target === modal) {
      closeModal();
    }
  });
});
</script>

Magyarázat: Az Escape billentyű megnyomása bezárja a modált és visszaadja a fókuszt az azt megnyitó elemnek. A fókusz megfelelően csapdázódik a modálban, de mindig van kilépési lehetőség.

2. Egyedi legördülő menü megfelelő kilépési lehetőségekkel

<div class="dropdown-example">
  <h3>Egyedi legördülő menü</h3>
  <div class="dropdown-container">
    <button id="dropdown-trigger" aria-haspopup="listbox" aria-expanded="false" class="dropdown-button">
      Válassz opciót ▼
    </button>
    
    <ul id="dropdown-menu" role="listbox" class="dropdown-menu" hidden>
      <li role="option" tabindex="0" data-value="option1">Első opció</li>
      <li role="option" tabindex="0" data-value="option2">Második opció</li>
      <li role="option" tabindex="0" data-value="option3">Harmadik opció</li>
      <li role="option" tabindex="0" data-value="option4">Negyedik opció</li>
    </ul>
  </div>
</div>

<style>
.dropdown-example {
  margin: 20px;
  max-width: 300px;
}

.dropdown-container {
  position: relative;
}

.dropdown-button {
  width: 100%;
  padding: 12px 16px;
  background-color: white;
  border: 2px solid #ced4da;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  text-align: left;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.dropdown-button:hover, .dropdown-button:focus {
  border-color: #007bff;
  outline: 2px solid #80bdff;
  outline-offset: 2px;
}

.dropdown-button[aria-expanded="true"] {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom-color: transparent;
}

.dropdown-menu {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background-color: white;
  border: 2px solid #007bff;
  border-top: none;
  border-radius: 0 0 4px 4px;
  list-style: none;
  margin: 0;
  padding: 0;
  z-index: 1000;
  max-height: 200px;
  overflow-y: auto;
}

.dropdown-menu[hidden] {
  display: none;
}

.dropdown-menu li {
  padding: 12px 16px;
  cursor: pointer;
  border-bottom: 1px solid #eee;
}

.dropdown-menu li:last-child {
  border-bottom: none;
}

.dropdown-menu li:hover,
.dropdown-menu li:focus {
  background-color: #f8f9fa;
  outline: 2px solid #007bff;
  outline-offset: -2px;
}

.dropdown-menu li[aria-selected="true"] {
  background-color: #007bff;
  color: white;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const dropdownTrigger = document.getElementById('dropdown-trigger');
  const dropdownMenu = document.getElementById('dropdown-menu');
  const options = dropdownMenu.querySelectorAll('[role="option"]');
  
  let isOpen = false;
  let currentIndex = -1;

  function openDropdown() {
    isOpen = true;
    dropdownMenu.hidden = false;
    dropdownTrigger.setAttribute('aria-expanded', 'true');
    
    // Első opcióra fókusz
    if (options.length > 0) {
      currentIndex = 0;
      options[currentIndex].focus();
    }
  }

  function closeDropdown() {
    isOpen = false;
    dropdownMenu.hidden = true;
    dropdownTrigger.setAttribute('aria-expanded', 'false');
    dropdownTrigger.focus();
    currentIndex = -1;
  }

  function selectOption(index) {
    const selectedOption = options[index];
    const value = selectedOption.getAttribute('data-value');
    const text = selectedOption.textContent;
    
    // Kiválasztott opció jelölése
    options.forEach(option => option.setAttribute('aria-selected', 'false'));
    selectedOption.setAttribute('aria-selected', 'true');
    
    // Gomb szövegének frissítése
    dropdownTrigger.innerHTML = text + ' ▼';
    
    closeDropdown();
  }

  // Trigger gomb események
  dropdownTrigger.addEventListener('click', function() {
    if (isOpen) {
      closeDropdown();
    } else {
      openDropdown();
    }
  });

  dropdownTrigger.addEventListener('keydown', function(e) {
    switch(e.key) {
      case 'Enter':
      case ' ':
      case 'ArrowDown':
        e.preventDefault();
        if (!isOpen) {
          openDropdown();
        }
        break;
      case 'ArrowUp':
        e.preventDefault();
        if (!isOpen) {
          openDropdown();
        }
        break;
      case 'Escape':
        if (isOpen) {
          e.preventDefault();
          closeDropdown();
        }
        break;
    }
  });

  // Opciók eseménykezelése
  options.forEach((option, index) => {
    option.addEventListener('click', function() {
      selectOption(index);
    });

    option.addEventListener('keydown', function(e) {
      switch(e.key) {
        case 'Enter':
        case ' ':
          e.preventDefault();
          selectOption(index);
          break;
        case 'ArrowDown':
          e.preventDefault();
          currentIndex = (index + 1) % options.length;
          options[currentIndex].focus();
          break;
        case 'ArrowUp':
          e.preventDefault();
          currentIndex = (index - 1 + options.length) % options.length;
          options[currentIndex].focus();
          break;
        case 'Escape':
          e.preventDefault();
          closeDropdown();
          break;
        case 'Tab':
          // Tab billentyű természetes viselkedése - kilép a dropdown-ból
          closeDropdown();
          break;
      }
    });
  });

  // Kívülre kattintás
  document.addEventListener('click', function(e) {
    if (!dropdownTrigger.contains(e.target) && !dropdownMenu.contains(e.target)) {
      if (isOpen) {
        closeDropdown();
      }
    }
  });
});
</script>

Magyarázat: Az egyedi legördülő menük esetén a felhasználók Tab billentyűvel természetesen ki tudnak lépni, és billentyűparancsok állnak rendelkezésre a bezáráshoz vagy kilépéshez.

3. Fókusz kezelés csapda nélkül

<div class="focus-management-example">
  <h3>Többlépéses varázsló</h3>
  <div class="wizard-container">
    <div class="wizard-steps">
      <span class="step active">1. Személyes adatok</span>
      <span class="step">2. Elérhetőség</span>
      <span class="step">3. Megerősítés</span>
    </div>
    
    <form class="wizard-form">
      <div id="step1" class="wizard-step active">
        <h4>Személyes adatok</h4>
        <label for="firstName">Keresztnév:</label>
        <input type="text" id="firstName" name="firstName">
        
        <label for="lastName">Vezetéknév:</label>
        <input type="text" id="lastName" name="lastName">
      </div>
      
      <div id="step2" class="wizard-step" hidden>
        <h4>Elérhetőség</h4>
        <label for="email">E-mail:</label>
        <input type="email" id="email" name="email">
        
        <label for="phone">Telefon:</label>
        <input type="tel" id="phone" name="phone">
      </div>
      
      <div id="step3" class="wizard-step" hidden>
        <h4>Megerősítés</h4>
        <p>Kérlek ellenőrizd az adatokat a folytatás előtt.</p>
        <div id="summary"></div>
      </div>
      
      <div class="wizard-actions">
        <button type="button" id="prevBtn" disabled>Előző</button>
        <button type="button" id="nextBtn">Következő</button>
        <button type="button" id="exitBtn">Kilépés</button>
      </div>
    </form>
  </div>
</div>

<style>
.focus-management-example {
  margin: 20px;
  max-width: 500px;
}

.wizard-container {
  border: 1px solid #dee2e6;
  border-radius: 8px;
  padding: 20px;
  background-color: #f8f9fa;
}

.wizard-steps {
  display: flex;
  justify-content: space-between;
  margin-bottom: 20px;
  padding-bottom: 10px;
  border-bottom: 1px solid #dee2e6;
}

.step {
  padding: 8px 12px;
  background-color: #e9ecef;
  border-radius: 4px;
  font-size: 14px;
  flex: 1;
  text-align: center;
  margin: 0 2px;
}

.step.active {
  background-color: #007bff;
  color: white;
}

.wizard-form {
  background-color: white;
  padding: 20px;
  border-radius: 4px;
}

.wizard-step label {
  display: block;
  margin: 10px 0 5px 0;
  font-weight: bold;
}

.wizard-step input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ced4da;
  border-radius: 4px;
  box-sizing: border-box;
  margin-bottom: 10px;
}

.wizard-step input:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  border-color: #007bff;
}

.wizard-actions {
  display: flex;
  gap: 10px;
  justify-content: space-between;
  margin-top: 20px;
}

.wizard-actions button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

#prevBtn {
  background-color: #6c757d;
  color: white;
}

#nextBtn {
  background-color: #007bff;
  color: white;
}

#exitBtn {
  background-color: #dc3545;
  color: white;
}

.wizard-actions button:disabled {
  background-color: #e9ecef;
  color: #6c757d;
  cursor: not-allowed;
}

.wizard-actions button:not(:disabled):hover,
.wizard-actions button:not(:disabled):focus {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

#prevBtn:not(:disabled):hover { background-color: #545b62; }
#nextBtn:not(:disabled):hover { background-color: #0056b3; }
#exitBtn:hover { background-color: #c82333; }
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const steps = document.querySelectorAll('.wizard-step');
  const stepIndicators = document.querySelectorAll('.step');
  const prevBtn = document.getElementById('prevBtn');
  const nextBtn = document.getElementById('nextBtn');
  const exitBtn = document.getElementById('exitBtn');
  
  let currentStep = 0;
  const totalSteps = steps.length;

  function showStep(stepIndex) {
    // Minden lépés elrejtése
    steps.forEach((step, index) => {
      step.hidden = index !== stepIndex;
      stepIndicators[index].classList.toggle('active', index === stepIndex);
    });

    // Gombok állapotának frissítése
    prevBtn.disabled = stepIndex === 0;
    nextBtn.textContent = stepIndex === totalSteps - 1 ? 'Befejezés' : 'Következő';

    // Fókusz az első beviteli mezőre az aktuális lépésben
    const firstInput = steps[stepIndex].querySelector('input');
    if (firstInput) {
      // Kis késleltetés a DOM frissítéséhez
      setTimeout(() => firstInput.focus(), 100);
    }
  }

  function nextStep() {
    if (currentStep < totalSteps - 1) {
      currentStep++;
      showStep(currentStep);
    } else {
      // Utolsó lépés - befejezés
      alert('Adatok sikeresen elmentve!');
      exitWizard();
    }
  }

  function prevStep() {
    if (currentStep > 0) {
      currentStep--;
      showStep(currentStep);
    }
  }

  function exitWizard() {
    // Varázsló bezárása és fókusz visszaadása
    if (confirm('Biztosan ki akarsz lépni? A nem mentett adatok elvesznek.')) {
      // Itt lehet a varázslót elrejteni vagy visszairányítani
      alert('Kilépés a varázslóból');
      // A valóságban itt fókusz visszaadása az eredeti elemre
      document.body.focus();
    }
  }

  // Eseménykezelők
  nextBtn.addEventListener('click', nextStep);
  prevBtn.addEventListener('click', prevStep);
  exitBtn.addEventListener('click', exitWizard);

  // Billentyűzetes kezelés
  document.addEventListener('keydown', function(e) {
    // Escape billentyű - kilépés
    if (e.key === 'Escape') {
      exitWizard();
    }
    
    // Ctrl+Enter - gyors továbblépés
    if (e.ctrlKey && e.key === 'Enter') {
      nextStep();
    }
  });

  // Tab kezelés - természetes navigáció engedélyezése
  document.addEventListener('keydown', function(e) {
    if (e.key === 'Tab') {
      // A Tab billentyű természetes viselkedése - NEM blokkoljuk
      // A felhasználó ki tud lépni a varázslóból Tab-bal
    }
  });

  // Kezdeti állapot
  showStep(currentStep);
});
</script>

Magyarázat: Kerüld a JavaScript-et, amely fókuszt csapdáz elemeken belül világos kilépési út nélkül. Mindig biztosíts alternatív billentyűzetes vezérlőket és természetes navigációs lehetőségeket.

4. Alternatív navigációs lehetőségek biztosítása

<div class="alternative-navigation">
  <h3>Komplex interaktív térkép</h3>
  <div class="map-container">
    <div id="interactive-map" class="map-area" tabindex="0" role="application" aria-label="Interaktív térkép">
      <div class="map-overlay">
        <p>Használd a nyílbillentyűket a navigációhoz, Space-t nagyításhoz, Escape-t a kilépéshez.</p>
        <div class="map-position">Pozíció: <span id="position">Központ</span></div>
      </div>
    </div>
    
    <div class="map-controls">
      <h4>Alternatív vezérlők</h4>
      <button id="center-map">Központ</button>
      <button id="zoom-in">Nagyítás</button>
      <button id="zoom-out">Kicsinyítés</button>
      <button id="exit-map">Kilépés a térképből</button>
    </div>
    
    <div class="map-list">
      <h4>Helyszínek listája (alternatív hozzáférés)</h4>
      <ul>
        <li><a href="#location1">Központi park</a></li>
        <li><a href="#location2">Múzeum</a></li>
        <li><a href="#location3">Könyvtár</a></li>
        <li><a href="#location4">Piac</a></li>
      </ul>
    </div>
  </div>
</div>

<style>
.alternative-navigation {
  margin: 20px;
  max-width: 600px;
}

.map-container {
  border: 1px solid #dee2e6;
  border-radius: 8px;
  overflow: hidden;
}

.map-area {
  width: 100%;
  height: 300px;
  background: linear-gradient(135deg, #74b9ff, #0984e3);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 18px;
}

.map-area:focus {
  outline: 4px solid #fff;
  outline-offset: -4px;
}

.map-overlay {
  text-align: center;
  background-color: rgba(0, 0, 0, 0.7);
  padding: 20px;
  border-radius: 8px;
}

.map-position {
  margin-top: 10px;
  font-weight: bold;
}

.map-controls {
  padding: 15px;
  background-color: #f8f9fa;
  border-bottom: 1px solid #dee2e6;
}

.map-controls h4 {
  margin: 0 0 10px 0;
  font-size: 14px;
}

.map-controls button {
  margin-right: 10px;
  margin-bottom: 5px;
  padding: 8px 16px;
  border: 1px solid #ced4da;
  background-color: white;
  border-radius: 4px;
  cursor: pointer;
}

.map-controls button:hover,
.map-controls button:focus {
  background-color: #e9ecef;
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

#exit-map {
  background-color: #dc3545;
  color: white;
  border-color: #dc3545;
}

#exit-map:hover,
#exit-map:focus {
  background-color: #c82333;
}

.map-list {
  padding: 15px;
  background-color: white;
}

.map-list h4 {
  margin: 0 0 10px 0;
  font-size: 14px;
}

.map-list ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.map-list li {
  margin-bottom: 5px;
}

.map-list a {
  display: block;
  padding: 8px 12px;
  color: #007bff;
  text-decoration: none;
  border-radius: 4px;
  border: 1px solid transparent;
}

.map-list a:hover,
.map-list a:focus {
  background-color: #f8f9fa;
  border-color: #007bff;
  outline: 2px solid #007bff;
  outline-offset: -2px;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const mapArea = document.getElementById('interactive-map');
  const positionDisplay = document.getElementById('position');
  const centerBtn = document.getElementById('center-map');
  const zoomInBtn = document.getElementById('zoom-in');
  const zoomOutBtn = document.getElementById('zoom-out');
  const exitBtn = document.getElementById('exit-map');
  
  let mapPosition = { x: 0, y: 0 };
  let zoomLevel = 1;
  let isMapFocused = false;

  function updatePosition() {
    let positionText = `X: ${mapPosition.x}, Y: ${mapPosition.y}, Zoom: ${zoomLevel}x`;
    if (mapPosition.x === 0 && mapPosition.y === 0) {
      positionText = 'Központ';
    }
    positionDisplay.textContent = positionText;
  }

  function exitMap() {
    // Fókusz elmozgatása a térképről
    exitBtn.focus();
    isMapFocused = false;
    
    // Vizuális jelzés a kilépésről
    mapArea.style.opacity = '0.7';
    setTimeout(() => {
      mapArea.style.opacity = '1';
    }, 200);
  }

  // Térkép fókusz események
  mapArea.addEventListener('focus', function() {
    isMapFocused = true;
  });

  mapArea.addEventListener('blur', function() {
    isMapFocused = false;
  });

  // Térkép billentyűzetes vezérlés
  mapArea.addEventListener('keydown', function(e) {
    if (!isMapFocused) return;

    switch(e.key) {
      case 'ArrowUp':
        e.preventDefault();
        mapPosition.y = Math.max(-10, mapPosition.y - 1);
        updatePosition();
        break;
      case 'ArrowDown':
        e.preventDefault();
        mapPosition.y = Math.min(10, mapPosition.y + 1);
        updatePosition();
        break;
      case 'ArrowLeft':
        e.preventDefault();
        mapPosition.x = Math.max(-10, mapPosition.x - 1);
        updatePosition();
        break;
      case 'ArrowRight':
        e.preventDefault();
        mapPosition.x = Math.min(10, mapPosition.x + 1);
        updatePosition();
        break;
      case ' ':
        e.preventDefault();
        zoomLevel = Math.min(5, zoomLevel + 0.5);
        updatePosition();
        break;
      case 'Enter':
        e.preventDefault();
        zoomLevel = Math.max(0.5, zoomLevel - 0.5);
        updatePosition();
        break;
      case 'Escape':
        e.preventDefault();
        exitMap();
        break;
      case 'Tab':
        // Tab billentyű természetes viselkedése - kilépés a térképből
        exitMap();
        break;
    }
  });

  // Alternatív vezérlő gombok
  centerBtn.addEventListener('click', function() {
    mapPosition = { x: 0, y: 0 };
    zoomLevel = 1;
    updatePosition();
    mapArea.focus();
  });

  zoomInBtn.addEventListener('click', function() {
    zoomLevel = Math.min(5, zoomLevel + 0.5);
    updatePosition();
  });

  zoomOutBtn.addEventListener('click', function() {
    zoomLevel = Math.max(0.5, zoomLevel - 0.5);
    updatePosition();
  });

  exitBtn.addEventListener('click', exitMap);

  // Helyszín linkek
  document.querySelectorAll('.map-list a').forEach(link => {
    link.addEventListener('click', function(e) {
      e.preventDefault();
      const locationName = this.textContent;
      alert(`Navigálás: ${locationName}`);
      // Itt lehetne a térkép pozícióját beállítani
    });
  });

  // Kezdeti állapot
  updatePosition();
});
</script>

Magyarázat: Komplex billentyűzetes interakciókat igénylő funkciók esetén biztosíts alternatív billentyűzetes hozzáférhető vezérlőket és mindig legyen egyszerű kilépési lehetőség.

Rossz gyakorlatok

Fókusz csapdázása

<!-- ROSSZ PÉLDA: Tab billentyű blokkolása -->
<div id="bad-trap-container" tabindex="0">
  <p>Ez egy rossz példa, ahol a fókusz csapdába esik</p>
  <button>Gomb</button>
</div>

<script>
// ROSSZ: Tab billentyű blokkolása a fókusz elem belsejében tartásához
document.getElementById('bad-trap-container').addEventListener('keydown', function(e) {
  if (e.key === 'Tab') {
    e.preventDefault(); // Fókusz csapdázása
    // A felhasználó nem tud kilépni innen
  }
});
</script>

Probléma: A Tab vagy Shift+Tab megakadályozása csapdába ejti a billentyűzet felhasználókat, akik nem tudnak kilépni a komponensből.

Escape billentyű kezelés hiánya

<!-- ROSSZ PÉLDA: Modál Escape kezelés nélkül -->
<div id="bad-modal" class="modal" style="display: block;">
  <div class="modal-content">
    <p>Ez a modál nem zárható be billentyűzettel</p>
    <!-- Nincs billentyűzetes bezárási lehetőség -->
  </div>
</div>

<script>
// ROSSZ: Nincs Escape kezelés
document.getElementById('bad-modal').addEventListener('keydown', function(e) {
  // Escape billentyű figyelmen kívül hagyása
  // Nincs módja a modál bezárásának billentyűzettel
});
</script>

Probléma: Modálok vagy overlay-ek, amelyek nem zárhatók be vagy nem lehet belőlük kilépni billentyűzettel, akadálymentességi problémát okoznak.

Rejtett fókuszálható elemek

<!-- ROSSZ PÉLDA: Láthatatlan elemek a tab sorrendben -->
<style>
.hidden-but-focusable {
  position: absolute;
  left: -9999px; /* Képernyőn kívül */
  /* VAGY */
  opacity: 0; /* Láthatatlan de fókuszálható */
}
</style>

<div>
  <button>Látható gomb</button>
  <button class="hidden-but-focusable">Rejtett de fókuszálható</button>
  <button>Másik látható gomb</button>
</div>

<script>
// ROSSZ: Fókusz mozgatása láthatatlan elemekre
function badFocusToHidden() {
  document.querySelector('.hidden-but-focusable').focus();
  // A felhasználó nem tudja, hol van a fókusz
}
</script>

Probléma: A fókusz láthatatlan vagy képernyőn kívüli elemekre mozgatása megzavarja a felhasználókat és fókusz csapdát okozhat.

Komplex billentyűparancsok alternatívák nélkül

<!-- ROSSZ PÉLDA: Csak komplex billentyűkombinációk -->
<div id="complex-widget" tabindex="0">
  <p>Komplex widget - csak Ctrl+Shift+Alt+billentyű kombinációkkal működik</p>
</div>

<script>
// ROSSZ: Csak nehéz billentyűkombinációk
document.getElementById('complex-widget').addEventListener('keydown', function(e) {
  // Csak bonyolult kombinációk
  if (e.ctrlKey && e.shiftKey && e.altKey && e.key === 'X') {
    // Kilépés csak ezzel a nehéz kombinációval
    this.blur();
  }
  
  // Nincs egyszerű alternatíva, mint Escape vagy Tab
  
  // Tab blokkolása
  if (e.key === 'Tab') {
    e.preventDefault(); // Csapda!
  }
});
</script>

Probléma: Olyan szekvenciák megkövetelése, amelyeket nehéz replikálni vagy lehetetlenné teszik a kilépést, akadálymentességi akadályokat hoz létre.

Források

Iratkozz fel hírlevelünkre!

Amennyiben szeretnél első kézből értesülni az új bejegyzésekről, iratkozz fel hírlevelünkre!

Ez a weboldal sütiket használ a böngészési élmény javítása és a webhely megfelelő működésének biztosítása érdekében. A webhely használatának folytatásával elismeri és elfogadja a sütik használatát.

Összes elfogadása Csak a szükségesek elfogadása