4.1.3 - Állapotüzenetek

Röviden a szabványpontról

A WCAG 2.2 Success Criterion 4.1.3 (Status Messages) megköveteli, hogy a weboldalakon megjelenő állapotüzenetek programozottan meghatározhatók legyenek, így a kisegítő technológiák (mint a képernyőolvasók) be tudják mutatni ezeket az üzeneteket a felhasználóknak anélkül, hogy elmozdítanák a billentyűzet fókuszt. Ez azt jelenti, hogy a kisegítő technológiákra támaszkodó felhasználók időben értesülnek a változásokról, vagy a rendszer visszajelzéseiről anélkül, hogy el kelljen navigáljanak és elveszítsék az információt, hogy meddig haladtak oldalon.

Cél: Biztosítani, hogy a képernyőolvasóra vagy más kisegítő technológiákra támaszkodó felhasználók értesüljenek a felület változásairól és állapotfrissítéseiről anélkül, hogy megszakítanánk jelenlegi interakciójukat vagy fókuszukat.

Mire vonatkozik: Dinamikus tartalomfrissítésekre, mint például űrlapvalidációs üzenetek, sikeres vagy hibás értesítések, és más élő állapotfrissítések, amelyek nem járnak fókuszváltással, de fontosak a felhasználó tájékoztatásához.

Kiket érint

Elsődleges felhasználók: Látássérült vagy vak emberek, akik képernyőolvasót használnak a tartalom navigálásához. Ők a programozottan elérhető állapotüzenetekre támaszkodnak, hogy értesüljenek az olyan változásokról, mint az űrlaphibák, beküldési eredmények vagy valós idejű frissítések.

Másodlagos előnyök: A kognitív fogyatékossággal élő felhasználók is profitálnak, mert a világos állapotüzenetek segítenek tisztázni a rendszer válaszait. Továbbá azok a felhasználók, akik több feladatot végeznek egyszerre vagy figyelemzavarral küzdenek, hasznot húznak a nem zavaró, hozzáférhető értesítésekből.

Tesztelés

  1. Képernyőolvasó tesztelés: Használj népszerű képernyőolvasókat (NVDA, JAWS, VoiceOver) annak megerősítésére, hogy az állapotüzenetek bejelentésre kerülnek megjelenésükkor anélkül, hogy elmozgatnák a billentyűzet fókuszt
  2. ARIA attribútumok vizsgálata: Ellenőrizd, hogy a dinamikus állapotüzenetek megfelelő ARIA szerepeket használnak, mint a role=”status” vagy aria-live=”polite”
  3. Billentyűzetes navigáció: Biztosítsd, hogy a billentyűzet fókusz stabil marad, amikor állapotüzenetek jelennek meg; az üzenetek ne lopják el a fókuszt
  4. Automatizált akadálymentességi tesztelő eszközök: Használj eszközöket, mint az axe DevTools a hiányzó vagy helytelen ARIA élő régiók észlelésére
  5. Kód vizsgálat: Tekintsd át a HTML és JavaScript kódot annak ellenőrzésére, hogy az állapotüzenetek élő régió szerepekkel rendelkező elemekben kerülnek beillesztésre vagy frissítésre

Jó gyakorlatok

1. Űrlap validációs visszajelzések

<!-- REGISZTRÁCIÓS ŰRLAP - Élő állapotüzenetekkel -->
<form id="regisztracio-form" class="user-form" novalidate>
  <div class="form-header">
    <h2>Új fiók létrehozása</h2>
    <p>Töltse ki az alábbi mezőket a regisztrációhoz</p>
  </div>
  
  <!-- E-mail mező valós idejű validációval -->
  <div class="form-group">
    <label for="email">E-mail cím</label>
    <input 
      type="email" 
      id="email" 
      name="email" 
      required
      aria-describedby="email-help email-status"
      autocomplete="email"
    >
    <span id="email-help" class="field-help">
      Használjon érvényes e-mail formátumot (pl. nev@domain.hu)
    </span>
    <!-- Élő állapot régió a validációs üzenetekhez -->
    <div 
      id="email-status" 
      role="status" 
      aria-live="polite"
      aria-atomic="true"
      class="status-message"
    ></div>
  </div>
  
  <!-- Jelszó mező erősség indikátorral -->
  <div class="form-group">
    <label for="jelszo">Jelszó</label>
    <input 
      type="password" 
      id="jelszo" 
      name="jelszo" 
      required
      minlength="8"
      aria-describedby="jelszo-help jelszo-strength"
      autocomplete="new-password"
    >
    <span id="jelszo-help" class="field-help">
      Legalább 8 karakter, tartalmaz számot és nagybetűt
    </span>
    <!-- Jelszó erősség élő visszajelzés -->
    <div 
      id="jelszo-strength" 
      role="status" 
      aria-live="polite"
      class="password-strength"
    ></div>
  </div>
  
  <!-- Felhasználónév elérhetőség ellenőrzés -->
  <div class="form-group">
    <label for="felhasznalonev">Felhasználónév</label>
    <input 
      type="text" 
      id="felhasznalonev" 
      name="felhasznalonev" 
      required
      minlength="3"
      maxlength="20"
      aria-describedby="felhasznalonev-help felhasznalonev-status"
      autocomplete="username"
    >
    <span id="felhasznalonev-help" class="field-help">
      3-20 karakter, csak betűk és számok
    </span>
    <!-- Felhasználónév elérhetőség állapot -->
    <div 
      id="felhasznalonev-status" 
      role="status" 
      aria-live="polite"
      aria-busy="false"
      class="availability-status"
    ></div>
  </div>
  
  <button type="submit" class="btn-primary">Regisztráció</button>
  
  <!-- Általános űrlap állapot üzenetek -->
  <div 
    id="form-status" 
    role="status" 
    aria-live="polite"
    aria-atomic="true"
    class="form-status-message"
  ></div>
</form>
<script>
  const emailInput = document.getElementById('email');
  const emailStatus = document.getElementById('email-status');
  const jelszoInput = document.getElementById('jelszo');
  const jelszoStrength = document.getElementById('jelszo-strength');
  const felhasznalonevInput = document.getElementById('felhasznalonev');
  const felhasznalonevStatus = document.getElementById('felhasznalonev-status');
  const formStatus = document.getElementById('form-status');
  
  // E-mail validáció
  let emailTimeout;
  emailInput.addEventListener('input', function() {
    clearTimeout(emailTimeout);
    
    // Kis késleltetés a gépelés befejezése után
    emailTimeout = setTimeout(() => {
      const email = this.value.trim();
      
      if (email === '') {
        emailStatus.textContent = '';
        emailStatus.className = 'status-message';
      } else if (!isValidEmail(email)) {
        emailStatus.textContent = 'Érvénytelen e-mail formátum. Kérjük, ellenőrizze.';
        emailStatus.className = 'status-message error';
      } else {
        emailStatus.textContent = 'Az e-mail cím formátuma megfelelő.';
        emailStatus.className = 'status-message success';
      }
    }, 500);
  });
  
  // Jelszó erősség ellenőrzés
  jelszoInput.addEventListener('input', function() {
    const jelszo = this.value;
    let erosseg = 0;
    let uzenet = '';
    
    if (jelszo.length === 0) {
      jelszoStrength.textContent = '';
      return;
    }
    
    // Erősség számítás
    if (jelszo.length >= 8) erosseg++;
    if (jelszo.length >= 12) erosseg++;
    if (/[a-z]/.test(jelszo)) erosseg++;
    if (/[A-Z]/.test(jelszo)) erosseg++;
    if (/[0-9]/.test(jelszo)) erosseg++;
    if (/[^a-zA-Z0-9]/.test(jelszo)) erosseg++;
    
    // Üzenet és stílus beállítása
    if (erosseg <= 2) {
      uzenet = 'Gyenge jelszó. Ajánlott több karaktertípus használata.';
      jelszoStrength.className = 'password-strength weak';
    } else if (erosseg <= 4) {
      uzenet = 'Közepes erősségű jelszó. Megfelelő a legtöbb célra.';
      jelszoStrength.className = 'password-strength medium';
    } else {
      uzenet = 'Erős jelszó. Kiváló választás!';
      jelszoStrength.className = 'password-strength strong';
    }
    
    jelszoStrength.textContent = uzenet;
  });
  
  // Felhasználónév elérhetőség ellenőrzés
  let nevTimeout;
  felhasznalonevInput.addEventListener('input', function() {
    clearTimeout(nevTimeout);
    const nev = this.value.trim();
    
    if (nev.length < 3) {
      felhasznalonevStatus.textContent = '';
      return;
    }
    
    // Ellenőrzés indítása
    felhasznalonevStatus.setAttribute('aria-busy', 'true');
    felhasznalonevStatus.textContent = 'Felhasználónév ellenőrzése...';
    felhasznalonevStatus.className = 'availability-status checking';
    
    // Szimulált szerver ellenőrzés
    nevTimeout = setTimeout(() => {
      felhasznalonevStatus.setAttribute('aria-busy', 'false');
      
      // Véletlenszerű eredmény szimulálása
      const foglalt = Math.random() > 0.7;
      
      if (foglalt) {
        felhasznalonevStatus.textContent = `A "${nev}" felhasználónév már foglalt. Próbáljon másikat.`;
        felhasznalonevStatus.className = 'availability-status unavailable';
      } else {
        felhasznalonevStatus.textContent = `A "${nev}" felhasználónév elérhető.`;
        felhasznalonevStatus.className = 'availability-status available';
      }
    }, 1000);
  });
  
  // Űrlap beküldés
  document.getElementById('regisztracio-form').addEventListener('submit', function(e) {
    e.preventDefault();
    
    // Küldés állapot
    formStatus.textContent = 'Regisztráció feldolgozása...';
    formStatus.className = 'form-status-message processing';
    
    // Szimulált feldolgozás
    setTimeout(() => {
      formStatus.textContent = 'Sikeres regisztráció! Aktiváló e-mail elküldve.';
      formStatus.className = 'form-status-message success';
      
      // Űrlap visszaállítása
      this.reset();
      
      // Mezők állapotának törlése
      emailStatus.textContent = '';
      jelszoStrength.textContent = '';
      felhasznalonevStatus.textContent = '';
    }, 2000);
  });
  
  function isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
</script>
<style>
  .status-message {
    margin-top: 4px;
    font-size: 14px;
    padding: 4px 8px;
    border-radius: 4px;
    min-height: 24px;
  }
  
  .status-message.error {
    background-color: #fee;
    color: #c00;
    border-left: 3px solid #c00;
  }
  
  .status-message.success {
    background-color: #efe;
    color: #060;
    border-left: 3px solid #060;
  }
  
  .password-strength {
    margin-top: 4px;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 14px;
  }
  
  .password-strength.weak {
    background-color: #fee;
    color: #c00;
  }
  
  .password-strength.medium {
    background-color: #ffc;
    color: #660;
  }
  
  .password-strength.strong {
    background-color: #efe;
    color: #060;
  }
  
  .availability-status {
    margin-top: 4px;
    font-size: 14px;
    padding: 4px 8px;
    border-radius: 4px;
  }
  
  .availability-status.checking {
    background-color: #eef;
    color: #006;
  }
  
  .availability-status.available {
    background-color: #efe;
    color: #060;
  }
  
  .availability-status.unavailable {
    background-color: #fee;
    color: #c00;
  }
  
  .form-status-message {
    margin-top: 16px;
    padding: 12px;
    border-radius: 4px;
    font-weight: bold;
  }
  
  .form-status-message.processing {
    background-color: #eef;
    color: #006;
  }
  
  .form-status-message.success {
    background-color: #efe;
    color: #060;
  }
</style>

2. Valós idejű frissítések és értesítések

<!-- ÉLŐBEN FRISSÜLŐ ALKALMAZÁS -->
<div class="live-app">
  <h2>Élő értesítések demonstráció</h2>
  
  <!-- Bevásárlókosár frissítések -->
  <div class="shopping-section">
    <h3>Bevásárlókosár</h3>
    <div class="cart-items">
      <p>Jelenlegi kosár: <span id="cart-count">0</span> termék</p>
    </div>
    
    <button type="button" id="add-to-cart" class="btn-primary">
      Termék hozzáadása
    </button>
    
    <!-- Kosár állapot üzenetek -->
    <div 
      id="cart-status" 
      role="status" 
      aria-live="polite"
      aria-relevant="additions text"
      class="cart-notifications"
    ></div>
  </div>
  
  <!-- Chat alkalmazás példa -->
  <div class="chat-section">
    <h3>Csevegés</h3>
    <div class="chat-window" aria-label="Csevegés ablak">
      <div id="chat-messages" class="messages">
        <p>Üdvözöljük a csevegésben!</p>
      </div>
    </div>
    
    <!-- Új üzenetek értesítése -->
    <div 
      id="chat-updates" 
      role="status" 
      aria-live="polite"
      aria-relevant="additions"
      class="sr-only"
    ></div>
    
    <button type="button" id="simulate-message" class="btn-secondary">
      Új üzenet szimulálása
    </button>
  </div>
  
  <!-- Letöltési folyamat -->
  <div class="download-section">
    <h3>Fájl letöltés</h3>
    <button type="button" id="start-download" class="btn-primary">
      Letöltés indítása
    </button>
    
    <!-- Letöltési állapot -->
    <div 
      id="download-progress" 
      role="status" 
      aria-live="polite"
      aria-relevant="all"
      class="download-status"
    ></div>
  </div>
  
  <!-- Automatikus mentés értesítés -->
  <div class="autosave-section">
    <h3>Szövegszerkesztő</h3>
    <textarea 
      id="editor" 
      rows="5" 
      cols="50"
      aria-describedby="autosave-status"
      placeholder="Kezdjen el gépelni..."
    ></textarea>
    
    <!-- Automatikus mentés állapot -->
    <div 
      id="autosave-status" 
      role="status" 
      aria-live="polite"
      aria-atomic="true"
      class="autosave-indicator"
    ></div>
  </div>
</div>
<script>
  // Bevásárlókosár példa
  let cartCount = 0;
  const cartStatus = document.getElementById('cart-status');
  const cartCountDisplay = document.getElementById('cart-count');
  
  document.getElementById('add-to-cart').addEventListener('click', function() {
    cartCount++;
    cartCountDisplay.textContent = cartCount;
    
    // Állapotüzenet frissítése
    cartStatus.textContent = `Termék hozzáadva a kosárhoz. Összesen ${cartCount} termék van a kosárban.`;
    
    // Üzenet eltűntetése 3 másodperc után
    setTimeout(() => {
      cartStatus.textContent = '';
    }, 3000);
  });
  
  // Chat példa
  const chatMessages = document.getElementById('chat-messages');
  const chatUpdates = document.getElementById('chat-updates');
  let messageCount = 0;
  
  document.getElementById('simulate-message').addEventListener('click', function() {
    messageCount++;
    const senderek = ['Anna', 'Béla', 'Csilla', 'Dénes'];
    const sender = senderek[Math.floor(Math.random() * senderek.length)];
    
    // Új üzenet hozzáadása
    const newMessage = document.createElement('p');
    newMessage.textContent = `${sender}: Szia! Ez az ${messageCount}. üzenet.`;
    chatMessages.appendChild(newMessage);
    
    // Képernyőolvasó értesítése
    chatUpdates.textContent = `Új üzenet ${sender}-tól/től`;
    
    // Scroll a legújabb üzenethez
    chatMessages.scrollTop = chatMessages.scrollHeight;
  });
  
  // Letöltés példa
  const downloadProgress = document.getElementById('download-progress');
  
  document.getElementById('start-download').addEventListener('click', function() {
    this.disabled = true;
    let progress = 0;
    
    // Kezdeti állapot
    downloadProgress.textContent = 'Letöltés megkezdve...';
    downloadProgress.className = 'download-status downloading';
    
    // Folyamat szimulálása
    const interval = setInterval(() => {
      progress += 20;
      
      if (progress < 100) {
        downloadProgress.textContent = `Letöltés folyamatban: ${progress}%`;
      } else {
        downloadProgress.textContent = 'Letöltés befejezve! A fájl elérhető a Letöltések mappában.';
        downloadProgress.className = 'download-status complete';
        clearInterval(interval);
        this.disabled = false;
      }
    }, 1000);
  });
  
  // Automatikus mentés példa
  const editor = document.getElementById('editor');
  const autosaveStatus = document.getElementById('autosave-status');
  let autosaveTimeout;
  let lastSaved = '';
  
  editor.addEventListener('input', function() {
    clearTimeout(autosaveTimeout);
    
    // Mentés ikon megjelenítése
    autosaveStatus.textContent = 'Módosítások...';
    autosaveStatus.className = 'autosave-indicator unsaved';
    
    // Automatikus mentés 2 másodperc inaktivitás után
    autosaveTimeout = setTimeout(() => {
      const currentText = editor.value;
      
      if (currentText !== lastSaved) {
        // Mentés szimulálása
        lastSaved = currentText;
        const time = new Date().toLocaleTimeString('hu-HU');
        
        autosaveStatus.textContent = `Automatikusan mentve: ${time}`;
        autosaveStatus.className = 'autosave-indicator saved';
        
        // Üzenet elhalványítása
        setTimeout(() => {
          autosaveStatus.classList.add('fade');
        }, 3000);
      }
    }, 2000);
  });
</script>
<style>
  .cart-notifications {
    margin-top: 8px;
    padding: 8px;
    background-color: #e3f2fd;
    color: #1976d2;
    border-radius: 4px;
    min-height: 32px;
  }
  
  .chat-window {
    border: 1px solid #ccc;
    height: 200px;
    overflow-y: auto;
    padding: 8px;
    margin-bottom: 8px;
    background-color: #f5f5f5;
  }
  
  .messages p {
    margin: 4px 0;
    padding: 4px 8px;
    background-color: white;
    border-radius: 4px;
  }
  
  .download-status {
    margin-top: 8px;
    padding: 8px;
    border-radius: 4px;
    font-weight: bold;
  }
  
  .download-status.downloading {
    background-color: #fff3cd;
    color: #856404;
  }
  
  .download-status.complete {
    background-color: #d4edda;
    color: #155724;
  }
  
  .autosave-indicator {
    margin-top: 4px;
    font-size: 14px;
    transition: opacity 0.3s;
  }
  
  .autosave-indicator.unsaved {
    color: #666;
    font-style: italic;
  }
  
  .autosave-indicator.saved {
    color: #28a745;
  }
  
  .autosave-indicator.fade {
    opacity: 0.5;
  }
  
  .sr-only {
    position: absolute;
    left: -10000px;
    width: 1px;
    height: 1px;
    overflow: hidden;
  }
</style>

3. Tömeges műveletek visszajelzései

<!-- FÁJLKEZELŐ ALKALMAZÁS -->
<div class="file-manager">
  <h2>Fájlkezelő</h2>
  
  <!-- Műveleti gombok -->
  <div class="toolbar">
    <button type="button" id="select-all" class="btn-secondary">
      Összes kijelölése
    </button>
    <button type="button" id="delete-selected" class="btn-danger" disabled>
      Kijelöltek törlése
    </button>
    <button type="button" id="move-selected" class="btn-secondary" disabled>
      Kijelöltek áthelyezése
    </button>
  </div>
  
  <!-- Fájl lista -->
  <div class="file-list" role="region" aria-label="Fájlok listája">
    <div class="file-item">
      <input type="checkbox" id="file1" class="file-checkbox">
      <label for="file1">dokumentum.pdf</label>
    </div>
    <div class="file-item">
      <input type="checkbox" id="file2" class="file-checkbox">
      <label for="file2">képek.zip</label>
    </div>
    <div class="file-item">
      <input type="checkbox" id="file3" class="file-checkbox">
      <label for="file3">táblázat.xlsx</label>
    </div>
  </div>
  
  <!-- Műveleti állapotok -->
  <div 
    id="file-operation-status" 
    role="status" 
    aria-live="polite"
    aria-atomic="true"
    class="operation-status"
  ></div>
  
  <!-- Visszavonás lehetőség -->
  <div 
    id="undo-region" 
    role="status" 
    aria-live="polite"
    class="undo-notification"
    style="display: none;"
  >
    <span id="undo-message"></span>
    <button type="button" id="undo-action" class="btn-link">
      Visszavonás
    </button>
  </div>
</div>
<!-- KÉPGALÉRIA FELTÖLTÉS -->
<div class="image-gallery">
  <h2>Képfeltöltés</h2>
  
  <div class="upload-area">
    <input 
      type="file" 
      id="file-upload" 
      multiple 
      accept="image/*"
      class="file-input"
      aria-describedby="upload-help upload-status"
    >
    <label for="file-upload" class="upload-label">
      Válasszon képeket a feltöltéshez
    </label>
    <span id="upload-help" class="help-text">
      Több fájl kiválasztásához használja a Ctrl/Cmd billentyűt
    </span>
  </div>
  
  <!-- Feltöltési állapot -->
  <div 
    id="upload-status" 
    role="status" 
    aria-live="polite"
    aria-busy="false"
    class="upload-progress"
  ></div>
  
  <!-- Feltöltött képek listája -->
  <div 
    id="uploaded-images" 
    role="region" 
    aria-label="Feltöltött képek"
    class="image-grid"
  ></div>
</div>
<script>
  // Fájlkezelő funkciók
  const fileCheckboxes = document.querySelectorAll('.file-checkbox');
  const deleteBtn = document.getElementById('delete-selected');
  const moveBtn = document.getElementById('move-selected');
  const fileOperationStatus = document.getElementById('file-operation-status');
  const undoRegion = document.getElementById('undo-region');
  const undoMessage = document.getElementById('undo-message');
  let deletedFiles = [];
  
  // Kijelölés kezelése
  function updateSelection() {
    const selected = document.querySelectorAll('.file-checkbox:checked').length;
    deleteBtn.disabled = selected === 0;
    moveBtn.disabled = selected === 0;
    
    if (selected > 0) {
      fileOperationStatus.textContent = `${selected} fájl kijelölve`;
    } else {
      fileOperationStatus.textContent = '';
    }
  }
  
  fileCheckboxes.forEach(checkbox => {
    checkbox.addEventListener('change', updateSelection);
  });
  
  // Összes kijelölése
  document.getElementById('select-all').addEventListener('click', function() {
    const allChecked = Array.from(fileCheckboxes).every(cb => cb.checked);
    
    fileCheckboxes.forEach(cb => {
      cb.checked = !allChecked;
    });
    
    updateSelection();
    
    if (!allChecked) {
      fileOperationStatus.textContent = 'Minden fájl kijelölve';
    } else {
      fileOperationStatus.textContent = 'Kijelölés megszüntetve';
    }
  });
  
  // Törlés művelet
  document.getElementById('delete-selected').addEventListener('click', function() {
    const selected = document.querySelectorAll('.file-checkbox:checked');
    deletedFiles = [];
    
    selected.forEach(checkbox => {
      const fileName = checkbox.nextElementSibling.textContent;
      deletedFiles.push({
        checkbox: checkbox,
        fileName: fileName,
        parent: checkbox.parentElement
      });
      checkbox.parentElement.style.display = 'none';
    });
    
    // Állapot frissítése
    fileOperationStatus.textContent = `${deletedFiles.length} fájl törölve`;
    
    // Visszavonás lehetőség megjelenítése
    undoMessage.textContent = `${deletedFiles.length} fájl törölve. `;
    undoRegion.style.display = 'block';
    
    // Gombok frissítése
    updateSelection();
    
    // Visszavonás elrejtése 10 másodperc után
    setTimeout(() => {
      undoRegion.style.display = 'none';
      deletedFiles = [];
    }, 10000);
  });
  
  // Visszavonás
  document.getElementById('undo-action').addEventListener('click', function() {
    deletedFiles.forEach(file => {
      file.parent.style.display = 'block';
      file.checkbox.checked = false;
    });
    
    fileOperationStatus.textContent = 'Törlés visszavonva';
    undoRegion.style.display = 'none';
    deletedFiles = [];
  });
  
  // Képfeltöltés példa
  const fileUpload = document.getElementById('file-upload');
  const uploadStatus = document.getElementById('upload-status');
  const uploadedImages = document.getElementById('uploaded-images');
  
  fileUpload.addEventListener('change', function(e) {
    const files = Array.from(e.target.files);
    
    if (files.length === 0) return;
    
    // Feltöltés indítása
    uploadStatus.setAttribute('aria-busy', 'true');
    uploadStatus.textContent = `${files.length} kép feltöltése megkezdve...`;
    uploadStatus.className = 'upload-progress uploading';
    
    let uploaded = 0;
    
    // Szimulált feltöltés
    files.forEach((file, index) => {
      setTimeout(() => {
        uploaded++;
        
        // Frissítés minden fájl után
        if (uploaded < files.length) {
          uploadStatus.textContent = `Feltöltés: ${uploaded}/${files.length} kép kész`;
        } else {
          uploadStatus.setAttribute('aria-busy', 'false');
          uploadStatus.textContent = `Minden kép sikeresen feltöltve (${files.length} fájl)`;
          uploadStatus.className = 'upload-progress complete';
          
          // Feltöltött képek megjelenítése
          const img = document.createElement('div');
          img.className = 'uploaded-image';
          img.textContent = `${files.length} új kép`;
          uploadedImages.appendChild(img);
        }
      }, (index + 1) * 1000);
    });
    
    // Input törlése
    this.value = '';
  });
</script>
<style>
  .operation-status {
    margin: 8px 0;
    padding: 8px;
    background-color: #e8f5e9;
    color: #2e7d32;
    border-radius: 4px;
    min-height: 32px;
  }
  
  .undo-notification {
    margin-top: 8px;
    padding: 8px;
    background-color: #fff3cd;
    border: 1px solid #ffeeba;
    border-radius: 4px;
  }
  
  .file-item {
    padding: 4px 0;
  }
  
  .upload-area {
    border: 2px dashed #ccc;
    padding: 20px;
    text-align: center;
    margin-bottom: 16px;
  }
  
  .file-input {
    display: none;
  }
  
  .upload-label {
    display: inline-block;
    padding: 8px 16px;
    background-color: #007bff;
    color: white;
    border-radius: 4px;
    cursor: pointer;
  }
  
  .upload-label:hover {
    background-color: #0056b3;
  }
  
  .upload-progress {
    margin-top: 8px;
    padding: 8px;
    border-radius: 4px;
    text-align: center;
  }
  
  .upload-progress.uploading {
    background-color: #cfe2ff;
    color: #084298;
  }
  
  .upload-progress.complete {
    background-color: #d1e7dd;
    color: #0f5132;
  }
  
  .image-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 8px;
    margin-top: 16px;
  }
  
  .uploaded-image {
    padding: 20px;
    background-color: #f0f0f0;
    text-align: center;
    border-radius: 4px;
  }
</style>

Rossz gyakorlatok

Kerülendő megoldások:

  • Fókuszt eltoló figyelmeztetések használata: A role=”alert” túl erős figyelmeztetés és elmozdítja a fókuszt, ami zavaró lehet, ha a felhasználó éppen gépel vagy navigál. Inkább a role=”status” használata az ajánlottabb a frissítésekhez, ami nem annyira zavaró
  • Tartalom frissítése ARIA élő régiók nélkül: Az ARIA élő attribútumok nélküli frissítések nem kerülnek bejelentésre a képernyőolvasók által
  • Billentyűzet fókusz szükségtelen áthelyezése állapotüzenetekre: A fókusz megváltoztatása összezavarhatja a billentyűzetes és képernyőolvasó felhasználókat
  • Túl gyakori vagy zajos állapotfrissítések: Minden apró változás bejelentése túlterhelheti a felhasználókat

1. Helytelen „alert” használat bemutatása

<!-- ROSSZ GYAKORLAT - Tolakodó alert használata -->
<form>
  <input type="email" id="email-input">
  <div id="alert" role="alert" style="display: none;"></div>
</form>

<script>
  document.getElementById('email-input').addEventListener('input', function() {
    const alertDiv = document.getElementById('alert');
    
    if (!this.value.includes('@')) {
      alertDiv.textContent = 'Hiba: Érvénytelen e-mail formátum!';
      alertDiv.style.display = 'block';
      // ROSSZ: Az alert megszakítja a felhasználót és elmozdítja a fókuszt
      // Normál gépelés közben ez zavaró
    }
  });
</script>

2. Tartalom frissítése ARIA élő régiók nélkül

<!-- ROSSZ GYAKORLAT - Élő régió nélküli frissítés -->
<div id="status"></div>
<button onclick="updateStatus()">Állapot frissítése</button>

<script>
  function updateStatus() {
    // Ez nem lesz bejelentve képernyőolvasók által
    document.getElementById('status').textContent = 'Frissítés elérhető.';
    // Hiányzik: role="status" vagy aria-live="polite"
  }
</script>

<!-- ROSSZ GYAKORLAT - Dinamikus értesítés megfelelő jelölés nélkül -->
<div class="notification-area">
  <!-- Nincs ARIA live régió -->
</div>

<script>
  function showNotification(message) {
    const notificationArea = document.querySelector('.notification-area');
    const notification = document.createElement('div');
    notification.textContent = message;
    notificationArea.appendChild(notification);
    // A képernyőolvasó nem értesül az új üzenetről
  }
</script>

3. Fókusz szükségtelen áthelyezése az állapotüzenetekre

<!-- ROSSZ GYAKORLAT - Fókusz áthelyezése -->
<form>
  <input type="text" id="name-input">
  <div id="message" tabindex="-1" style="display: none;"></div>
</form>
<script>
  function showMessage(text) {
    const msg = document.getElementById('message');
    msg.textContent = text;
    msg.style.display = 'block';
    msg.focus(); // ROSSZ: Ne mozgasd a fókuszt állapotüzenetekre
    // A felhasználó elveszíti a helyét az űrlapban
  }
  
  document.getElementById('name-input').addEventListener('blur', function() {
    if (this.value.length < 3) {
      showMessage('A név túl rövid!');
      // A fókusz elmozdul, ami zavaró
    }
  });
</script>
<!-- ROSSZ GYAKORLAT - Automatikus fókusz értesítésekre -->
<div class="toast-container"></div>
<script>
  function showToast(message) {
    const container = document.querySelector('.toast-container');
    const toast = document.createElement('div');
    toast.className = 'toast';
    toast.textContent = message;
    toast.tabIndex = -1;
    container.appendChild(toast);
    
    // ROSSZ: Fókusz elmozdítása az értesítésre
    toast.focus();
    
    setTimeout(() => {
      container.removeChild(toast);
    }, 3000);
  }
</script>

4. Túl gyakori vagy zavaró állapotfrissítések

<!-- ROSSZ GYAKORLAT - Túl gyakori frissítések -->
<div role="status" aria-live="polite" id="counter"></div>
<div role="status" aria-live="polite" id="timer"></div>

<script>
  let count = 0;
  setInterval(() => {
    // Minden másodpercben frissít - túl zajos
    document.getElementById('counter').textContent = `Számláló: ${count++}`;
  }, 1000);
  
  // Még egy másodpercenkénti frissítés
  setInterval(() => {
    const now = new Date();
    document.getElementById('timer').textContent = `Idő: ${now.toLocaleTimeString()}`;
    // Ez is túl gyakori és zavaró
  }, 1000);
</script>

<!-- ROSSZ GYAKORLAT - Minden apró változás bejelentése -->
<input type="text" id="search-input">
<div role="status" aria-live="polite" id="search-status"></div>

<script>
  document.getElementById('search-input').addEventListener('input', function() {
    const searchStatus = document.getElementById('search-status');
    // ROSSZ: Minden karakternél frissít
    searchStatus.textContent = `${this.value.length} karakter beírva`;
    // Ez túlságosan zajos a képernyőolvasóknál
  });
</script>

5. Nem frissülnek az automatikus összetett üzenetek

<!-- ROSSZ GYAKORLAT - Nem automatikus frissítések -->
<div role="status" aria-live="polite">
  <span id="part1">Állapot:</span>
  <span id="part2"></span>
  <span id="part3"></span>
</div>

<script>
  function updateStatus(status, progress) {
    // ROSSZ: Részletes frissítések külön-külön
    document.getElementById('part2').textContent = status;
    setTimeout(() => {
      document.getElementById('part3').textContent = ` - ${progress}%`;
      // A képernyőolvasó csak részeket hall, összezavaró lehet
    }, 100);
  }
</script>

<!-- ROSSZ GYAKORLAT - Többszörös élő régiók egyszerre -->
<div role="status" aria-live="polite" id="status1"></div>
<div role="status" aria-live="polite" id="status2"></div>
<div role="status" aria-live="polite" id="status3"></div>

<script>
  function showMultipleMessages() {
    // ROSSZ: Egyszerre több üzenet - zavaró
    document.getElementById('status1').textContent = 'Első üzenet';
    document.getElementById('status2').textContent = 'Második üzenet';
    document.getElementById('status3').textContent = 'Harmadik üzenet';
    // A képernyőolvasó egyszerre próbálja bejelenteni mindet
  }
</script>

6. Hiányzó aria-atomic attribútum összetett tartalmaknál

<!-- ROSSZ GYAKORLAT - Hiányzó aria-atomic -->
<div role="status" aria-live="polite" id="complex-status">
  <span class="icon">✓</span>
  <span class="message">Sikeres művelet</span>
  <span class="details">5 elemből 3 feldolgozva</span>
</div>

<script>
  function updateComplexStatus(message, details) {
    const statusDiv = document.getElementById('complex-status');
    
    // ROSSZ: Részleges frissítés aria-atomic nélkül
    statusDiv.querySelector('.message').textContent = message;
    statusDiv.querySelector('.details').textContent = details;
    // A képernyőolvasó lehet hogy csak a változott részt mondja be
    // Hiányzik: aria-atomic="true"
  }
</script>

<!-- ROSSZ GYAKORLAT - Strukturált tartalom rossz kezelése -->
<div role="status" aria-live="polite">
  <ul id="status-list">
    <li>Állandó elem</li>
  </ul>
</div>

<script>
  function addStatusItem(text) {
    const list = document.getElementById('status-list');
    const newItem = document.createElement('li');
    newItem.textContent = text;
    list.appendChild(newItem);
    // Lehet hogy csak az új elem kerül bejelentésre, kontextus nélkül
  }
</script>

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