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
- 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
- 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
- Képernyőolvasó teszt: Teszteld képernyőolvasóval, hogy a javaslatok világosan és időben bejelentésre kerülnek-e
- 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)
- 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!