3.3.3 - Javaslatok hibák javításához

Röviden a szabványpontról

A WCAG 2.2 Success Criterion 3.3.3 (Error Suggestion) megköveteli, hogy amikor a rendszer automatikusan felismer egy felhasználói beviteli hibát, javaslatokat nyújtson a javításához, kivéve ha ez veszélyeztetné a biztonságot vagy a tartalom célját. Ez vonatkozik minden űrlapra és interaktív tartalomra, ahol a felhasználók adatokat küldenek be, mint például regisztrációs űrlapok, keresődobozok vagy vásárlási folyamatok.

Cél: Segíteni azokat a felhasználókat, akik hibákat ejtenek űrlapok kitöltése, vagy adatok bevitele során azzal, hogy világos, végrehajtható javaslatokat kapnak a javításra. Ez csökkenti a frusztrációt és javítja az akadálymentességet kognitív fogyatékossággal, memóriaproblémákkal vagy korlátozott technikai készségekkel rendelkező felhasználók számára.

Mire vonatkozik: Minden olyan webes tartalomra, amely felhasználói adatbevitelt fogad és automatikus hibafelfedezéssel rendelkezik, beleértve az űrlapvalidációt, keresési funkciókat és adatfeldolgozó rendszereket.

Kiket érint

Elsődleges felhasználók: Kognitív fogyatékossággal, tanulási nehézségekkel vagy memóriaproblémákkal élő emberek, akik nehezen azonosítják vagy javítják ki a hibákat útmutatás nélkül.

Másodlagos előnyök: Minden felhasználó számára előnyös a hasznos hibajavítási javaslatok, beleértve azokat, akik nem ismerik az űrlapot, korlátozott nyelvi készségekkel rendelkeznek, vagy mobil eszközöket használnak, ahol gyakoribbak a gépelési hibák.

Tesztelés

  1. Ismert beviteli hibák kiváltása: Írj be érvénytelen adatokat (pl. hibás e-mail formátum) és ellenőrizd, hogy a rendszer tesz-e javaslatot a javításra, vagy nyújt-e hasznos útmutatást
  2. Billentyűzetes navigáció: Használj csak billentyűzetet annak ellenörzésére, hogy a hibajavítási javaslatok egér nélkül is elérhetőek-e
  3. Képernyőolvasó teszt: Teszteld képernyőolvasóval, hogy a javaslatok világosan és időben bejelentésre kerülnek-e
  4. Biztonsági kivételek ellenőrzése: Győződj meg róla, hogy nem adnak javaslatokat olyan esetekben, amikor az veszélyeztetné a biztonságot (pl. jelszó mezők)
  5. Specifikus és végrehajtható üzenetek: Ellenőrizd, hogy a hibaüzenetek konkrétak és végrehajthathatóak legyenek, ne általánosítsanak

Példák jó gyakorlatra

1. E-mail mező hibás kitöltésének felismerése és javaslatok a javításra

<!-- REGISZTRÁCIÓS ŰRLAP - Intelligens hibajavítási javaslatokkal -->
<form class="registration-form" novalidate>
  <div class="form-header">
    <h2>Fiók regisztráció</h2>
    <p>Töltse ki az alábbi mezőket új fiókja létrehozásához.</p>
  </div>
  
  <div class="form-group">
    <label for="username">Felhasználónév *</label>
    <input 
      type="text" 
      id="username" 
      name="username" 
      required
      aria-describedby="username-help username-error"
      autocomplete="username"
    >
    <div id="username-help" class="field-help">
      3-20 karakter, csak betűk és számok
    </div>
    <div id="username-error" class="error-message" role="alert" aria-live="polite" style="display: none;"></div>
  </div>
  
  <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-error"
      autocomplete="email"
    >
    <div id="email-help" class="field-help">
      Valós e-mail címét adja meg (pl. nev@domain.hu)
    </div>
    <div id="email-error" class="error-message" role="alert" aria-live="assertive" style="display: none;"></div>
  </div>
  
  <div class="form-group">
    <label for="phone">Telefonszám *</label>
    <input 
      type="tel" 
      id="phone" 
      name="phone" 
      required
      aria-describedby="phone-help phone-error"
      autocomplete="tel"
      placeholder="+36 1 234 5678"
    >
    <div id="phone-help" class="field-help">
      Magyar vagy nemzetközi formátumban
    </div>
    <div id="phone-error" class="error-message" role="alert" aria-live="polite" style="display: none;"></div>
  </div>
  
  <div class="form-group">
    <label for="website">Weboldal (opcionális)</label>
    <input 
      type="url" 
      id="website" 
      name="website"
      aria-describedby="website-help website-error"
      placeholder="https://example.com"
    >
    <div id="website-help" class="field-help">
      Teljes URL-t adjon meg (http:// vagy https://)
    </div>
    <div id="website-error" class="error-message" role="alert" aria-live="polite" style="display: none;"></div>
  </div>
  
  <div class="form-group">
    <label for="birth-date">Születési dátum *</label>
    <input 
      type="date" 
      id="birth-date" 
      name="birth-date" 
      required
      aria-describedby="birth-date-help birth-date-error"
      max="2006-01-01"
    >
    <div id="birth-date-help" class="field-help">
      Legalább 18 évesnek kell lennie
    </div>
    <div id="birth-date-error" class="error-message" role="alert" aria-live="polite" style="display: none;"></div>
  </div>
  
  <div class="form-actions">
    <button type="submit" class="btn-submit">Regisztráció</button>
    <button type="reset" class="btn-reset">Törlés</button>
  </div>
</form>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const form = document.querySelector('.registration-form');
  
  // Gyakori e-mail domainok listája javításhoz
  const commonDomains = [
    'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 
    'freemail.hu', 'citromail.hu', 't-online.hu', 'invitel.hu'
  ];
  
  // Gyakori e-mail hibák és javításaik
  const emailCorrections = {
    'gmial.com': 'gmail.com',
    'gmai.com': 'gmail.com',
    'gmail.co': 'gmail.com',
    'yahooo.com': 'yahoo.com',
    'yaho.com': 'yahoo.com',
    'hotmial.com': 'hotmail.com',
    'hotmai.com': 'hotmail.com',
    'outlok.com': 'outlook.com',
    'outlook.co': 'outlook.com'
  };
  
  // Valós idejű validáció minden mezőre
  const inputs = form.querySelectorAll('input[required], input[type="email"], input[type="url"], input[type="tel"]');
  
  inputs.forEach(input => {
    input.addEventListener('input', function() {
      validateFieldWithSuggestions(input);
    });
    
    input.addEventListener('blur', function() {
      validateFieldWithSuggestions(input);
    });
  });
  
  function validateFieldWithSuggestions(input) {
    const errorElement = document.getElementById(input.id + '-error');
    clearError(input, errorElement);
    
    const value = input.value.trim();
    
    if (!value && input.hasAttribute('required')) {
      showError(input, errorElement, `A ${getFieldLabel(input)} megadása kötelező.`);
      return;
    }
    
    if (!value) return; // Ha üres és nem kötelező, nincs validáció
    
    switch (input.type) {
      case 'email':
        validateEmailWithSuggestions(input, errorElement, value);
        break;
      case 'tel':
        validatePhoneWithSuggestions(input, errorElement, value);
        break;
      case 'url':
        validateUrlWithSuggestions(input, errorElement, value);
        break;
      case 'text':
        if (input.id === 'username') {
          validateUsernameWithSuggestions(input, errorElement, value);
        }
        break;
      case 'date':
        validateDateWithSuggestions(input, errorElement, value);
        break;
    }
  }
  
  function validateEmailWithSuggestions(input, errorElement, email) {
    // Alapvető e-mail formátum ellenőrzés
    if (!email.includes('@')) {
      showError(input, errorElement, 'Hiányzik a "@" jel. Például: nev@domain.hu');
      return;
    }
    
    const parts = email.split('@');
    if (parts.length !== 2) {
      showError(input, errorElement, 'Túl sok "@" jel. Egy e-mail címben csak egy "@" lehet.');
      return;
    }
    
    const [localPart, domain] = parts;
    
    if (!localPart) {
      showError(input, errorElement, 'Hiányzik a felhasználónév a "@" jel előtt. Például: nev@domain.hu');
      return;
    }
    
    if (!domain) {
      showError(input, errorElement, 'Hiányzik a domain a "@" jel után. Például: nev@domain.hu');
      return;
    }
    
    if (!domain.includes('.')) {
      showError(input, errorElement, 'A domain-ben hiányzik a pont. Például: nev@domain.hu');
      return;
    }
    
    // Domain javítási javaslat ellenőrzése
    const lowerDomain = domain.toLowerCase();
    if (emailCorrections[lowerDomain]) {
      const suggestion = `${localPart}@${emailCorrections[lowerDomain]}`;
      showErrorWithSuggestion(
        input, 
        errorElement, 
        `Esetleg ezt akarta írni: "${suggestion}"?`,
        suggestion
      );
      return;
    }
    
    // Elgépelés ellenőrzése közös domainekben
    const domainSuggestion = findClosestDomain(lowerDomain);
    if (domainSuggestion && domainSuggestion !== lowerDomain) {
      const suggestion = `${localPart}@${domainSuggestion}`;
      showErrorWithSuggestion(
        input, 
        errorElement, 
        `Esetleg ezt akarta írni: "${suggestion}"?`,
        suggestion
      );
      return;
    }
    
    // Teljes e-mail formátum validáció
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
      showError(input, errorElement, 'Kérjük, adjon meg egy érvényes e-mail címet. Például: nev@domain.hu');
    }
  }
  
  function validatePhoneWithSuggestions(input, errorElement, phone) {
    // Csak számok, szóközök, kötőjelek és zárójelek megengedettek
    const cleanPhone = phone.replace(/[\s\-\(\)\+]/g, '');
    
    if (!/^[0-9]+$/.test(cleanPhone)) {
      showError(input, errorElement, 'A telefonszám csak számokat tartalmazhat. Távolítson el minden betűt és speciális karaktert.');
      return;
    }
    
    if (cleanPhone.length < 7) {
      showError(input, errorElement, 'A telefonszám túl rövid. Magyar szám: 06 1 234 5678 vagy nemzetközi: +36 1 234 5678');
      return;
    }
    
    // Magyar telefonszám formátum javaslat
    if (cleanPhone.startsWith('06') && cleanPhone.length === 11) {
      const formatted = `+36 ${cleanPhone.substring(2, 3)} ${cleanPhone.substring(3, 6)} ${cleanPhone.substring(6)}`;
      showErrorWithSuggestion(
        input, 
        errorElement, 
        `Javasolt formátum: "${formatted}"`,
        formatted
      );
      return;
    }
    
    if (cleanPhone.startsWith('36') && cleanPhone.length === 11) {
      const formatted = `+36 ${cleanPhone.substring(2, 3)} ${cleanPhone.substring(3, 6)} ${cleanPhone.substring(6)}`;
      showErrorWithSuggestion(
        input, 
        errorElement, 
        `Hiányzik a "+" jel. Javasolt: "${formatted}"`,
        formatted
      );
      return;
    }
  }
  
  function validateUrlWithSuggestions(input, errorElement, url) {
    if (!url.startsWith('http://') && !url.startsWith('https://')) {
      const suggestion = `https://${url}`;
      showErrorWithSuggestion(
        input, 
        errorElement, 
        `Hiányzik a protokoll. Esetleg: "${suggestion}"?`,
        suggestion
      );
      return;
    }
    
    try {
      new URL(url);
    } catch (e) {
      showError(input, errorElement, 'Érvénytelen URL formátum. Például: https://www.example.com');
    }
  }
  
  function validateUsernameWithSuggestions(input, errorElement, username) {
    if (username.length < 3) {
      showError(input, errorElement, 'A felhasználónév túl rövid. Legalább 3 karakter szükséges.');
      return;
    }
    
    if (username.length > 20) {
      showError(input, errorElement, 'A felhasználónév túl hosszú. Maximum 20 karakter engedélyezett.');
      return;
    }
    
    if (!/^[a-zA-Z0-9]+$/.test(username)) {
      // Számoljuk meg a nem engedélyezett karaktereket
      const invalidChars = username.match(/[^a-zA-Z0-9]/g);
      if (invalidChars) {
        const suggestion = username.replace(/[^a-zA-Z0-9]/g, '');
        showErrorWithSuggestion(
          input, 
          errorElement, 
          `Távolítsa el a speciális karaktereket. Javaslat: "${suggestion}"`,
          suggestion
        );
      }
      return;
    }
  }
  
  function validateDateWithSuggestions(input, errorElement, date) {
    const selectedDate = new Date(date);
    const today = new Date();
    const age = today.getFullYear() - selectedDate.getFullYear();
    const monthDiff = today.getMonth() - selectedDate.getMonth();
    
    if (selectedDate > today) {
      showError(input, errorElement, 'A születési dátum nem lehet a jövőben. Ellenőrizze az évet.');
      return;
    }
    
    if (age < 18 || (age === 18 && monthDiff < 0)) {
      showError(input, errorElement, 'Legalább 18 évesnek kell lennie a regisztrációhoz.');
      return;
    }
    
    if (age > 120) {
      showError(input, errorElement, 'Kérjük, ellenőrizze a születési dátumot. Az év valószínűleg helytelen.');
      return;
    }
  }
  
  function findClosestDomain(domain) {
    // Egyszerű Levenshtein távolság alapú keresés
    let closest = null;
    let minDistance = Infinity;
    
    commonDomains.forEach(commonDomain => {
      const distance = levenshteinDistance(domain, commonDomain);
      if (distance < minDistance && distance <= 2) {
        minDistance = distance;
        closest = commonDomain;
      }
    });
    
    return closest;
  }
  
  function levenshteinDistance(str1, str2) {
    const matrix = [];
    
    for (let i = 0; i <= str2.length; i++) {
      matrix[i] = [i];
    }
    
    for (let j = 0; j <= str1.length; j++) {
      matrix[0][j] = j;
    }
    
    for (let i = 1; i <= str2.length; i++) {
      for (let j = 1; j <= str1.length; j++) {
        if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
          matrix[i][j] = matrix[i - 1][j - 1];
        } else {
          matrix[i][j] = Math.min(
            matrix[i - 1][j - 1] + 1,
            matrix[i][j - 1] + 1,
            matrix[i - 1][j] + 1
          );
        }
      }
    }
    
    return matrix[str2.length][str1.length];
  }
  
  function showError(input, errorElement, message) {
    input.setAttribute('aria-invalid', 'true');
    input.classList.add('field-error');
    errorElement.style.display = 'block';
    errorElement.innerHTML = `<span class="error-text">${message}</span>`;
  }
  
  function showErrorWithSuggestion(input, errorElement, message, suggestion) {
    input.setAttribute('aria-invalid', 'true');
    input.classList.add('field-error');
    errorElement.style.display = 'block';
    errorElement.innerHTML = `
      <span class="error-text">${message}</span>
      <button type="button" class="suggestion-btn" onclick="applySuggestion('${input.id}', '${suggestion}')">
        Alkalmazás
      </button>
    `;
  }
  
  function clearError(input, errorElement) {
    input.setAttribute('aria-invalid', 'false');
    input.classList.remove('field-error');
    errorElement.style.display = 'none';
    errorElement.innerHTML = '';
  }
  
  function getFieldLabel(input) {
    const label = document.querySelector(`label[for="${input.id}"]`);
    return label ? label.textContent.replace(' *', '').toLowerCase() : 'mező';
  }
  
  // Globális függvény a javaslatok alkalmazásához
  window.applySuggestion = function(inputId, suggestion) {
    const input = document.getElementById(inputId);
    const errorElement = document.getElementById(inputId + '-error');
    
    input.value = suggestion;
    clearError(input, errorElement);
    input.focus();
    
    // Képernyőolvasó értesítése
    announceToScreenReader(`Javaslat alkalmazva: ${suggestion}`);
  };
  
  function announceToScreenReader(message) {
    const announcement = document.createElement('div');
    announcement.setAttribute('role', 'status');
    announcement.setAttribute('aria-live', 'polite');
    announcement.className = 'visually-hidden';
    announcement.textContent = message;
    document.body.appendChild(announcement);
    
    setTimeout(() => {
      if (announcement.parentNode) {
        document.body.removeChild(announcement);
      }
    }, 1000);
  }
});
</script>

<style>
.registration-form {
  max-width: 600px;
  margin: 2rem auto;
  padding: 2rem;
  background: white;
  border: 1px solid #e9ecef;
  border-radius: 8px;
}

.form-header {
  margin-bottom: 2rem;
  text-align: center;
}

.form-header h2 {
  margin: 0 0 0.5rem 0;
  color: #495057;
}

.form-header p {
  margin: 0;
  color: #6c757d;
}

.form-group {
  margin-bottom: 1.5rem;
}

.form-group label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.5rem;
  color: #495057;
}

.form-group input {
  width: 100%;
  padding: 0.75rem;
  border: 2px solid #ced4da;
  border-radius: 4px;
  font-size: 1rem;
  transition: border-color 0.2s ease;
}

.form-group input:focus {
  outline: none;
  border-color: #007bff;
  box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}

.form-group input.field-error {
  border-color: #dc3545;
  background-color: #fff5f5;
}

.field-help {
  margin-top: 0.5rem;
  font-size: 0.9em;
  color: #6c757d;
  line-height: 1.4;
}

.error-message {
  margin-top: 0.5rem;
  padding: 0.75rem;
  background: #f8d7da;
  border: 1px solid #f5c6cb;
  border-radius: 4px;
  color: #721c24;
  font-size: 0.9em;
  line-height: 1.4;
}

.error-text {
  display: block;
  margin-bottom: 0.5rem;
}

.suggestion-btn {
  background: #28a745;
  color: white;
  border: none;
  padding: 0.25rem 0.75rem;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.8em;
  transition: background-color 0.2s ease;
}

.suggestion-btn:hover, .suggestion-btn:focus {
  background: #218838;
  outline: 2px solid #fff;
  outline-offset: 1px;
}

.form-actions {
  display: flex;
  gap: 1rem;
  margin-top: 2rem;
  justify-content: center;
}

.btn-submit, .btn-reset {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
  transition: background-color 0.2s ease;
}

.btn-submit {
  background: #007bff;
  color: white;
}

.btn-submit:hover, .btn-submit:focus {
  background: #0056b3;
}

.btn-reset {
  background: #6c757d;
  color: white;
}

.btn-reset:hover, .btn-reset:focus {
  background: #545b62;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 768px) {
  .registration-form {
    margin: 1rem;
    padding: 1rem;
  }
  
  .form-actions {
    flex-direction: column;
  }
}
</style>

Magyarázat: A rendszer valós időben felismeri a gyakori hibákat és konkrét javítási javaslatokat nyújt, amelyeket a felhasználó egy gombnyomással alkalmazhat.

2. Jelszómezők biztonsági szempontjai (nincs javaslat)

<!-- JELSZÓ KEZELÉS - Biztonságos hibaüzenetek javaslet nélkül -->
<form class="password-form">
  <div class="password-section">
    <h2>Jelszó módosítása</h2>
    <p>Adja meg jelenlegi jelszavát és az új jelszót.</p>
  </div>
  
  <div class="form-group">
    <label for="current-password">Jelenlegi jelszó *</label>
    <input 
      type="password" 
      id="current-password" 
      name="current-password" 
      required
      aria-describedby="current-password-error"
      autocomplete="current-password"
    >
    <div id="current-password-error" class="error-message" role="alert" aria-live="assertive" style="display: none;"></div>
  </div>
  
  <div class="form-group">
    <label for="new-password">Új jelszó *</label>
    <input 
      type="password" 
      id="new-password" 
      name="new-password" 
      required
      aria-describedby="password-requirements new-password-error"
      autocomplete="new-password"
    >
    <div id="password-requirements" class="password-requirements">
      <h4>Jelszó követelmények:</h4>
      <ul id="requirements-list">
        <li id="req-length" class="requirement">
          <span class="requirement-status" aria-hidden="true">⚬</span>
          Legalább 8 karakter hosszú
        </li>
        <li id="req-uppercase" class="requirement">
          <span class="requirement-status" aria-hidden="true">⚬</span>
          Legalább egy nagybetű (A-Z)
        </li>
        <li id="req-lowercase" class="requirement">
          <span class="requirement-status" aria-hidden="true">⚬</span>
          Legalább egy kisbetű (a-z)
        </li>
        <li id="req-number" class="requirement">
          <span class="requirement-status" aria-hidden="true">⚬</span>
          Legalább egy szám (0-9)
        </li>
        <li id="req-special" class="requirement">
          <span class="requirement-status" aria-hidden="true">⚬</span>
          Legalább egy speciális karakter (!@#$%^&*)
        </li>
      </ul>
    </div>
    <div id="new-password-error" class="error-message" role="alert" aria-live="assertive" style="display: none;"></div>
  </div>
  
  <div class="form-group">
    <label for="confirm-password">Jelszó megerősítése *</label>
    <input 
      type="password" 
      id="confirm-password" 
      name="confirm-password" 
      required
      aria-describedby="confirm-password-error"
      autocomplete="new-password"
    >
    <div id="confirm-password-error" class="error-message" role="alert" aria-live="polite" style="display: none;"></div>
  </div>
  
  <div class="form-actions">
    <button type="submit" class="btn-update">Jelszó frissítése</button>
    <button type="button" class="btn-cancel">Mégse</button>
  </div>
</form>

<script>
document.addEventListener('DOMContentLoaded', function() {
  const newPasswordInput = document.getElementById('new-password');
  const confirmPasswordInput = document.getElementById('confirm-password');
  const currentPasswordInput = document.getElementById('current-password');
  
  // Jelszó erősség ellenőrzése (NINCS konkrét javaslat biztonsági okokból)
  newPasswordInput.addEventListener('input', function() {
    validatePasswordStrength(this.value);
  });
  
  // Jelszó megerősítés ellenőrzése
  confirmPasswordInput.addEventListener('input', function() {
    validatePasswordConfirmation();
  });
  
  // Jelenlegi jelszó ellenőrzése
  currentPasswordInput.addEventListener('blur', function() {
    validateCurrentPassword();
  });
  
  function validatePasswordStrength(password) {
    const requirements = {
      length: password.length >= 8,
      uppercase: /[A-Z]/.test(password),
      lowercase: /[a-z]/.test(password),
      number: /[0-9]/.test(password),
      special: /[!@#$%^&*]/.test(password)
    };
    
    // Követelmények vizuális frissítése
    updateRequirementStatus('req-length', requirements.length);
    updateRequirementStatus('req-uppercase', requirements.uppercase);
    updateRequirementStatus('req-lowercase', requirements.lowercase);
    updateRequirementStatus('req-number', requirements.number);
    updateRequirementStatus('req-special', requirements.special);
    
    // Hibaüzenet kezelése (NINCS konkrét javaslat!)
    const errorElement = document.getElementById('new-password-error');
    
    if (!password) {
      clearError(newPasswordInput, errorElement);
      return;
    }
    
    const unmetRequirements = [];
    if (!requirements.length) unmetRequirements.push('legalább 8 karakter');
    if (!requirements.uppercase) unmetRequirements.push('legalább egy nagybetű');
    if (!requirements.lowercase) unmetRequirements.push('legalább egy kisbetű');
    if (!requirements.number) unmetRequirements.push('legalább egy szám');
    if (!requirements.special) unmetRequirements.push('legalább egy speciális karakter');
    
    if (unmetRequirements.length > 0) {
      showError(
        newPasswordInput, 
        errorElement, 
        `A jelszó nem teljesíti a követelményeket: ${unmetRequirements.join(', ')}.`
      );
    } else {
      clearError(newPasswordInput, errorElement);
    }
  }
  
  function validatePasswordConfirmation() {
    const password = newPasswordInput.value;
    const confirmation = confirmPasswordInput.value;
    const errorElement = document.getElementById('confirm-password-error');
    
    if (!confirmation) {
      clearError(confirmPasswordInput, errorElement);
      return;
    }
    
    if (password !== confirmation) {
      // BIZTONSÁGI SZEMPONTBÓL: Nincs konkrét javaslat, csak általános üzenet
      showError(
        confirmPasswordInput, 
        errorElement, 
        'A két jelszó nem egyezik meg. Kérjük, írja be újra a jelszót.'
      );
    } else {
      clearError(confirmPasswordInput, errorElement);
    }
  }
  
  function validateCurrentPassword() {
    const password = currentPasswordInput.value;
    const errorElement = document.getElementById('current-password-error');
    
    if (!password) {
      showError(
        currentPasswordInput, 
        errorElement, 
        'Kérjük, adja meg jelenlegi jelszavát.'
      );
    } else {
      clearError(currentPasswordInput, errorElement);
      
      // Szimulált jelszó ellenőrzés (valós alkalmazásban szerveroldali)
      // BIZTONSÁGI SZEMPONTBÓL: Nincs konkrét visszajelzés a jelszó tartalmáról
      setTimeout(() => {
        // Szimulált hibás jelszó
        if (password === 'wrongpassword') {
          showError(
            currentPasswordInput, 
            errorElement, 
            'A megadott jelszó helytelen. Kérjük, próbálja újra.'
          );
        }
      }, 500);
    }
  }
  
  function updateRequirementStatus(requirementId, isMet) {
    const requirement = document.getElementById(requirementId);
    const statusIcon = requirement.querySelector('.requirement-status');
    
    if (isMet) {
      requirement.classList.add('requirement-met');
      requirement.classList.remove('requirement-unmet');
      statusIcon.textContent = '✓';
      statusIcon.setAttribute('aria-label', 'teljesítve');
    } else {
      requirement.classList.add('requirement-unmet');
      requirement.classList.remove('requirement-met');
      statusIcon.textContent = '⚬';
      statusIcon.setAttribute('aria-label', 'nem teljesítve');
    }
  }
  
  function showError(input, errorElement, message) {
    input.setAttribute('aria-invalid', 'true');
    input.classList.add('field-error');
    errorElement.style.display = 'block';
    errorElement.textContent = message;
  }
  
  function clearError(input, errorElement) {
    input.setAttribute('aria-invalid', 'false');
    input.classList.remove('field-error');
    errorElement.style.display = 'none';
    errorElement.textContent = '';
  }
});
</script>

<style>
.password-form {
  max-width: 500px;
  margin: 2rem auto;
  padding: 2rem;
  background: white;
  border: 1px solid #e9ecef;
  border-radius: 8px;
}

.password-section {
  margin-bottom: 2rem;
  text-align: center;
}

.password-section h2 {
  margin: 0 0 0.5rem 0;
  color: #495057;
}

.password-section p {
  margin: 0;
  color: #6c757d;
}

.password-requirements {
  margin-top: 1rem;
  padding: 1rem;
  background: #f8f9fa;
  border: 1px solid #e9ecef;
  border-radius: 6px;
}

.password-requirements h4 {
  margin: 0 0 0.75rem 0;
  color: #495057;
  font-size: 1em;
}

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

.requirement {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
  padding: 0.25rem;
  border-radius: 4px;
  transition: all 0.2s ease;
}

.requirement-status {
  font-weight: bold;
  width: 16px;
  text-align: center;
}

.requirement-met {
  background: #d4edda;
  color: #155724;
}

.requirement-met .requirement-status {
  color: #28a745;
}

.requirement-unmet {
  background: #f8d7da;
  color: #721c24;
}

.requirement-unmet .requirement-status {
  color: #dc3545;
}

.btn-update, .btn-cancel {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
}

.btn-update {
  background: #28a745;
  color: white;
}

.btn-cancel {
  background: #6c757d;
  color: white;
}

/* A többi CSS ugyanaz, mint az előző példában */
</style>

Magyarázat: Jelszó mezőknél biztonsági okokból nem adunk konkrét javítási javaslatokat, csak általános útmutatást és követelmény-ellenőrzést végzünk.

3. Automatikus kiegészítés és keresési javaslatok

<!-- CÍMKERESÉS - Automatikus javaslatok és hibás írás kezelése -->
<div class="address-search-section">
  <h2>Cím keresése</h2>
  <p>Kezdje el gépelni a várost vagy az utcát és válasszon a javaslatokból.</p>
  
  <form class="address-form">
    <div class="form-group">
      <label for="city-search">Város *</label>
      <div class="autocomplete-container">
        <input 
          type="text" 
          id="city-search" 
          name="city"
          required
          aria-describedby="city-help city-error"
          aria-expanded="false"
          aria-autocomplete="list"
          aria-owns="city-suggestions"
          autocomplete="address-level2"
          placeholder="Pl. Budapest, Szeged, Debrecen..."
        >
        <ul id="city-suggestions" class="suggestions-list" role="listbox" aria-label="Város javaslatok" style="display: none;"></ul>
      </div>
      <div id="city-help" class="field-help">
        Gépelés közben javaslatok jelennek meg
      </div>
      <div id="city-error" class="error-message" role="alert" aria-live="assertive" style="display: none;"></div>
    </div>
    
    <div class="form-group">
      <label for="street-search">Utca és házszám *</label>
      <div class="autocomplete-container">
        <input 
          type="text" 
          id="street-search" 
          name="street"
          required
          aria-describedby="street-help street-error"
          aria-expanded="false"
          aria-autocomplete="list"
          aria-owns="street-suggestions"
          autocomplete="address-line1"
          placeholder="Pl. Váci út 1-3., Fő utca 12."
        >
        <ul id="street-suggestions" class="suggestions-list" role="listbox" aria-label="Utca javaslatok" style="display: none;"></ul>
      </div>
      <div id="street-help" class="field-help">
        Utca neve és házszám együtt
      </div>
      <div id="street-error" class="error-message" role="alert" aria-live="polite" style="display: none;"></div>
    </div>
    
    <div class="form-group">
      <label for="postal-code">Irányítószám *</label>
      <input 
        type="text" 
        id="postal-code" 
        name="postal-code"
        required
        pattern="[0-9]{4}"
        maxlength="4"
        aria-describedby="postal-help postal-error"
        autocomplete="postal-code"
        placeholder="1234"
      >
      <div id="postal-help" class="field-help">
        4 számjegy, például: 1011
      </div>
      <div id="postal-error" class="error-message" role="alert" aria-live="polite" style="display: none;"></div>
    </div>
    
    <div class="form-actions">
      <button type="submit" class="btn-search">Cím validálása</button>
    </div>
  </form>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
  // Magyar városok adatbázisa (részleges)
  const hungarianCities = [
    'Budapest', 'Debrecen', 'Szeged', 'Miskolc', 'Pécs', 'Győr', 'Nyíregyháza', 
    'Kecskemét', 'Székesfehérvár', 'Szombathely', 'Érd', 'Tatabánya', 'Kaposvár',
    'Sopron', 'Veszprém', 'Békéscsaba', 'Zalaegerszeg', 'Szolnok', 'Eger',
    'Nagykanizsa', 'Dunakeszi', 'Hódmezővásárhely', 'Cegléd', 'Baja'
  ];
  
  // Gyakori utcanév részek
  const commonStreetNames = [
    'Váci út', 'Fő utca', 'Kossuth utca', 'Petőfi utca', 'Szabadság tér',
    'Rákóczi út', 'József Attila utca', 'Dózsa György út', 'Ady Endre utca',
    'Árpád utca', 'Bajcsy-Zsilinszky út', 'Szent István utca'
  ];
  
  // Irányítószám-város párositások (részleges)
  const postalCodes = {
    '1011': 'Budapest I. kerület',
    '1051': 'Budapest V. kerület', 
    '4025': 'Debrecen',
    '6720': 'Szeged',
    '3525': 'Miskolc',
    '7621': 'Pécs'
  };
  
  let selectedSuggestionIndex = -1;
  let currentSuggestions = [];
  
  setupAutocomplete('city-search', 'city-suggestions', hungarianCities);
  setupAutocomplete('street-search', 'street-suggestions', commonStreetNames);
  setupPostalCodeValidation();
  
  function setupAutocomplete(inputId, suggestionsId, dataSource) {
    const input = document.getElementById(inputId);
    const suggestionsContainer = document.getElementById(suggestionsId);
    const errorElement = document.getElementById(inputId.replace('-search', '-error'));
    
    input.addEventListener('input', function() {
      const query = this.value.trim();
      
      if (query.length < 2) {
        hideSuggestions(suggestionsContainer, input);
        clearError(input, errorElement);
        return;
      }
      
      const suggestions = findSuggestions(query, dataSource);
      
      if (suggestions.length === 0) {
        // Nincs egyezés - keressünk hasonló találatokat
        const similarSuggestions = findSimilarSuggestions(query, dataSource);
        
        if (similarSuggestions.length > 0) {
          showErrorWithSuggestions(
            input, 
            errorElement, 
            `Nem található "${query}". Esetleg ezek közül valamelyik?`,
            similarSuggestions,
            suggestionsContainer
          );
        } else {
          showError(
            input, 
            errorElement, 
            `Nem található "${query}". Kérjük, ellenőrizze a helyesírást vagy próbáljon más keresőszót.`
          );
          hideSuggestions(suggestionsContainer, input);
        }
      } else {
        clearError(input, errorElement);
        showSuggestions(suggestions, suggestionsContainer, input);
      }
    });
    
    input.addEventListener('keydown', function(event) {
      handleKeyboardNavigation(event, input, suggestionsContainer);
    });
    
    input.addEventListener('blur', function() {
      // Késleltetett elrejtés, hogy a kattintás működjön
      setTimeout(() => {
        hideSuggestions(suggestionsContainer, input);
      }, 200);
    });
  }
  
  function findSuggestions(query, dataSource) {
    const lowerQuery = query.toLowerCase();
    return dataSource.filter(item => 
      item.toLowerCase().includes(lowerQuery)
    ).slice(0, 8); // Maximum 8 javaslat
  }
  
  function findSimilarSuggestions(query, dataSource) {
    const suggestions = [];
    const lowerQuery = query.toLowerCase();
    
    dataSource.forEach(item => {
      const lowerItem = item.toLowerCase();
      
      // Kezdődik ugyanazzal a betűvel
      if (lowerItem.charAt(0) === lowerQuery.charAt(0)) {
        suggestions.push(item);
      }
      
      // Tartalmazza a query első 2-3 karakterét
      if (query.length >= 3 && lowerItem.includes(lowerQuery.substring(0, 3))) {
        suggestions.push(item);
      }
      
      // Levenshtein távolság alapú keresés
      if (levenshteinDistance(lowerQuery, lowerItem) <= 2) {
        suggestions.push(item);
      }
    });
    
    // Duplikációk eltávolítása és limitálás
    return [...new Set(suggestions)].slice(0, 5);
  }
  
  function showSuggestions(suggestions, container, input) {
    currentSuggestions = suggestions;
    selectedSuggestionIndex = -1;
    
    container.innerHTML = '';
    
    suggestions.forEach((suggestion, index) => {
      const li = document.createElement('li');
      li.className = 'suggestion-item';
      li.textContent = suggestion;
      li.setAttribute('role', 'option');
      li.setAttribute('aria-selected', 'false');
      li.setAttribute('id', `suggestion-${index}`);
      
      li.addEventListener('click', function() {
        selectSuggestion(suggestion, input, container);
      });
      
      container.appendChild(li);
    });
    
    container.style.display = 'block';
    input.setAttribute('aria-expanded', 'true');
  }
  
  function showErrorWithSuggestions(input, errorElement, message, suggestions, container) {
    showError(input, errorElement, message);
    showSuggestions(suggestions, container, input);
  }
  
  function hideSuggestions(container, input) {
    container.style.display = 'none';
    input.setAttribute('aria-expanded', 'false');
    selectedSuggestionIndex = -1;
    currentSuggestions = [];
  }
  
  function selectSuggestion(suggestion, input, container) {
    input.value = suggestion;
    hideSuggestions(container, input);
    
    // Error törlése
    const errorElement = document.getElementById(input.id.replace('-search', '-error'));
    clearError(input, errorElement);
    
    // Képernyőolvasó értesítése
    announceToScreenReader(`Kiválasztva: ${suggestion}`);
    
    // Következő mezőre fókusz
    const nextInput = getNextInput(input);
    if (nextInput) {
      nextInput.focus();
    }
  }
  
  function handleKeyboardNavigation(event, input, container) {
    if (currentSuggestions.length === 0) return;
    
    switch (event.key) {
      case 'ArrowDown':
        event.preventDefault();
        selectedSuggestionIndex = Math.min(selectedSuggestionIndex + 1, currentSuggestions.length - 1);
        updateSuggestionSelection(container);
        break;
        
      case 'ArrowUp':
        event.preventDefault();
        selectedSuggestionIndex = Math.max(selectedSuggestionIndex - 1, -1);
        updateSuggestionSelection(container);
        break;
        
      case 'Enter':
        if (selectedSuggestionIndex >= 0) {
          event.preventDefault();
          const selectedSuggestion = currentSuggestions[selectedSuggestionIndex];
          selectSuggestion(selectedSuggestion, input, container);
        }
        break;
        
      case 'Escape':
        hideSuggestions(container, input);
        break;
    }
  }
  
  function updateSuggestionSelection(container) {
    const suggestions = container.querySelectorAll('.suggestion-item');
    
    suggestions.forEach((suggestion, index) => {
      if (index === selectedSuggestionIndex) {
        suggestion.classList.add('suggestion-selected');
        suggestion.setAttribute('aria-selected', 'true');
        suggestion.scrollIntoView({ block: 'nearest' });
      } else {
        suggestion.classList.remove('suggestion-selected');
        suggestion.setAttribute('aria-selected', 'false');
      }
    });
  }
  
  function setupPostalCodeValidation() {
    const postalInput = document.getElementById('postal-code');
    const cityInput = document.getElementById('city-search');
    const errorElement = document.getElementById('postal-error');
    
    postalInput.addEventListener('input', function() {
      const postalCode = this.value;
      
      // Csak számok engedélyezése
      if (!/^[0-9]*$/.test(postalCode)) {
        this.value = postalCode.replace(/[^0-9]/g, '');
        showError(
          this, 
          errorElement, 
          'Az irányítószám csak számokat tartalmazhat. Betűk és speciális karakterek eltávolítva.'
        );
        return;
      }
      
      if (postalCode.length === 4) {
        // Ellenőrizzük, hogy a város egyezik-e
        const associatedCity = postalCodes[postalCode];
        const selectedCity = cityInput.value;
        
        if (associatedCity && selectedCity && !associatedCity.toLowerCase().includes(selectedCity.toLowerCase())) {
          showErrorWithSuggestion(
            this,
            errorElement,
            `A ${postalCode} irányítószám ${associatedCity} területére vonatkozik. Szeretné módosítani a várost?`,
            associatedCity.split(' ')[0] // Csak a város neve
          );
        } else {
          clearError(this, errorElement);
        }
      } else if (postalCode.length > 0) {
        clearError(this, errorElement);
      }
    });
  }
  
  function showErrorWithSuggestion(input, errorElement, message, citySuggestion) {
    input.setAttribute('aria-invalid', 'true');
    input.classList.add('field-error');
    errorElement.style.display = 'block';
    errorElement.innerHTML = `
      <span class="error-text">${message}</span>
      <button type="button" class="suggestion-btn" onclick="applyCitySuggestion('${citySuggestion}')">
        Igen, ${citySuggestion}
      </button>
    `;
  }
  
  // Globális függvények
  window.applyCitySuggestion = function(city) {
    const cityInput = document.getElementById('city-search');
    const postalInput = document.getElementById('postal-code');
    const postalError = document.getElementById('postal-error');
    
    cityInput.value = city;
    clearError(postalInput, postalError);
    announceToScreenReader(`Város módosítva: ${city}`);
  };
  
  function getNextInput(currentInput) {
    const inputs = document.querySelectorAll('input');
    const currentIndex = Array.from(inputs).indexOf(currentInput);
    return inputs[currentIndex + 1] || null;
  }
  
  function levenshteinDistance(str1, str2) {
    const matrix = [];
    
    for (let i = 0; i <= str2.length; i++) {
      matrix[i] = [i];
    }
    
    for (let j = 0; j <= str1.length; j++) {
      matrix[0][j] = j;
    }
    
    for (let i = 1; i <= str2.length; i++) {
      for (let j = 1; j <= str1.length; j++) {
        if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
          matrix[i][j] = matrix[i - 1][j - 1];
        } else {
          matrix[i][j] = Math.min(
            matrix[i - 1][j - 1] + 1,
            matrix[i][j - 1] + 1,
            matrix[i - 1][j] + 1
          );
        }
      }
    }
    
    return matrix[str2.length][str1.length];
  }
  
  function showError(input, errorElement, message) {
    input.setAttribute('aria-invalid', 'true');
    input.classList.add('field-error');
    errorElement.style.display = 'block';
    errorElement.textContent = message;
  }
  
  function clearError(input, errorElement) {
    input.setAttribute('aria-invalid', 'false');
    input.classList.remove('field-error');
    errorElement.style.display = 'none';
    errorElement.textContent = '';
  }
  
  function announceToScreenReader(message) {
    const announcement = document.createElement('div');
    announcement.setAttribute('role', 'status');
    announcement.setAttribute('aria-live', 'polite');
    announcement.className = 'visually-hidden';
    announcement.textContent = message;
    document.body.appendChild(announcement);
    
    setTimeout(() => {
      if (announcement.parentNode) {
        document.body.removeChild(announcement);
      }
    }, 1000);
  }
});
</script>

<style>
.address-search-section {
  max-width: 600px;
  margin: 2rem auto;
  padding: 2rem;
  background: white;
  border: 1px solid #e9ecef;
  border-radius: 8px;
}

.autocomplete-container {
  position: relative;
}

.suggestions-list {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: white;
  border: 1px solid #ced4da;
  border-top: none;
  border-radius: 0 0 4px 4px;
  max-height: 200px;
  overflow-y: auto;
  z-index: 1000;
  list-style: none;
  padding: 0;
  margin: 0;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.suggestion-item {
  padding: 0.75rem;
  cursor: pointer;
  border-bottom: 1px solid #f8f9fa;
  transition: background-color 0.2s ease;
}

.suggestion-item:hover, .suggestion-item.suggestion-selected {
  background: #e9ecef;
}

.suggestion-item:last-child {
  border-bottom: none;
}

.btn-search {
  background: #007bff;
  color: white;
  border: none;
  padding: 0.75rem 1.5rem;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
}

.btn-search:hover, .btn-search:focus {
  background: #0056b3;
}

/* További stílusok az előző példákból */
</style>

Magyarázat: Az automatikus kiegészítés funkció felismeri a hibás bevitelt és javításokat javasol, valamint összekapcsolja a kapcsolódó mezőket (irányítószám-város).

Rossz gyakorlatok

Általános hibaüzenetek javaslatok nélkül

<!-- HIBÁS: Homályos és nem segítő hibaüzenetek -->
<form>
  <label for="email-bad">E-mail:</label>
  <input type="email" id="email-bad" required>
  <!-- HIBA: Általános, nem segítő üzenet -->
  <div role="alert" style="color: red; display: none;">Érvénytelen bevitel</div>
  
  <label for="phone-bad">Telefon:</label>
  <input type="tel" id="phone-bad" required>
  <!-- HIBA: Nem mondja meg, mi a probléma -->
  <div role="alert" style="color: red; display: none;">Hiba történt</div>
  
  <label for="date-bad">Dátum:</label>
  <input type="date" id="date-bad" required>
  <!-- HIBA: Nem ad útmutatást -->
  <div role="alert" style="color: red; display: none;">Helytelen formátum</div>
</form>

<!-- HIBÁS: Nincs hibaüzenet egyáltalán -->
<form>
  <input type="email" placeholder="E-mail cím" required>
  <button type="submit">Küldés</button>
  <!-- Semmi visszajelzés hibás formátum esetén -->
</form>

Probléma: A felhasználók nem tudják, mi volt a hiba és hogyan javítsák ki, ami frusztrációhoz és újbóli hibákhoz vezet.

Biztonsági információk felfedése

<!-- HIBÁS: Jelszó javaslatok biztonsági kockázattal -->
<form>
  <label for="password-insecure">Jelszó:</label>
  <input type="password" id="password-insecure" required>
  
  <script>
    // HIBA: Konkrét jelszó javaslatok
    function validatePassword(password) {
      if (password === 'password123') {
        showError('Túl gyakori jelszó. Próbálja: "MySecure2024!"'); // ROSSZ!
      }
      if (password.includes('admin')) {
        showError('Ne használja az "admin" szót. Próbálja: "SuperUser2024"'); // ROSSZ!
      }
    }
  </script>
</form>

<!-- HIBÁS: Felhasználónév létezésének felfedése -->
<form>
  <label for="username-insecure">Felhasználónév:</label>
  <input type="text" id="username-insecure" required>
  
  <script>
    // HIBA: Biztonsági információ szivárgás
    function checkUsername(username) {
      if (usernameExists(username)) {
        showError('Ez a felhasználónév már létezik. Próbálja: "' + username + '123"'); // ROSSZ!
      }
    }
  </script>
</form>

<!-- HIBÁS: Személyes adatok felfedése -->
<form>
  <label for="ssn-bad">Személyi szám:</label>
  <input type="text" id="ssn-bad" required>
  
  <script>
    // HIBA: Részleges személyi szám felfedése
    function validateSSN(ssn) {
      if (isInvalidSSN(ssn)) {
        showError('Érvénytelen személyi szám. A helyes formátum: 123456AB'); // RÉSZBEN ROSSZ
      }
    }
  </script>
</form>

Probléma: A túl specifikus javaslatok biztonsági kockázatot jelentenek, mert felfedhetnek érzékeny információkat vagy segíthetnek támadóknak.

Nem hozzáférhető hibaüzenetek

<!-- HIBÁS: Hibaüzenetek nem kapcsolódnak a mezőkhöz -->
<form>
  <label for="field1">Mező 1:</label>
  <input type="text" id="field1" required>
  
  <label for="field2">Mező 2:</label>
  <input type="email" id="field2" required>
  
  <!-- HIBA: Hibaüzenetek nincsenek programozottan kapcsolva -->
  <div id="errors" style="color: red; display: none;">
    <p>Mező 1 hibás</p>
    <p>E-mail formátum helytelen</p>
  </div>
</form>

<!-- HIBÁS: Hibaüzenetek nem bejelentettek -->
<form>
  <input type="email" id="email-silent" required>
  <!-- HIBA: Nincs role="alert" vagy aria-live -->
  <div id="email-error-silent" style="color: red; display: none;">
    Hibás e-mail formátum
  </div>
</form>

<!-- HIBÁS: Vizuális hibajelzés képernyőolvasók számára elérhetetlen -->
<form>
  <input type="text" id="visual-only" style="border: 2px solid red;" required>
  <!-- HIBA: Csak vizuális jelzés, nincs szöveges hibaüzenet -->
</form>

<!-- HIBÁS: JavaScript tooltip hibaüzenetek -->
<form>
  <input type="text" id="tooltip-error" required 
         title="Ez a mező hibás" > <!-- Title attribútum nem megfelelő hibaüzenethez -->
  
  <script>
    // HIBA: Tooltip-alapú hibaüzenetek nem akadálymentesek
    function showTooltipError(element, message) {
      element.setAttribute('title', message); // Nem jó módszer!
    }
  </script>
</form>

Probléma: A képernyőolvasók nem tudják felolvasni a hibaüzeneteket, vagy nem tudják összekapcsolni azokat a megfelelő mezőkkel.

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