3.3.2 - Címkék vagy útmutatók

Röviden a szabványpontról

A WCAG 2.2 Success Criterion 3.3.2 (Labels or Instructions) megköveteli, hogy címkéket vagy utasításokat adjunk meg, amikor a tartalom felhasználói bevitelt igényel. Ez azt jelenti, hogy minden űrlapmezőnek, interaktív vezérlőnek és adatbeviteli pontnak világos, érthető címkékkel vagy útmutatókkal kell rendelkeznie, amelyek megmondják a felhasználónak, mit kell tennie vagy milyen adatokat kell megadnia.

Cél: Biztosítani, hogy a felhasználók mindig tudják, mit várunk tőlük egy adott beviteli mezőnél vagy interakciónál. Ez különösen fontos azoknak a felhasználóknak, akik nehezen értelmezik a kontextust vagy kisegítő technológiákat használnak, amelyek csak a programozott címkéket tudják felolvasni.

Mire vonatkozik: Minden olyan webes tartalomra, amely felhasználói bevitelt vár, beleértve az űrlapmezőket, keresődobozokat, feltöltési területeket, interaktív widgeteket és bármilyen olyan elemet, ahol a felhasználónak adatot kell megadnia vagy műveletet kell végrehajtania.

Kiket érint

Elsődleges felhasználók: Képernyőolvasó felhasználók, akik csak a programozott címkéket hallják. Kognitív fogyatékossággal vagy tanulási nehézségekkel élő emberek, akiknek szükségük van világos útmutatásra. Gyengénlátó felhasználók, akik nagyítást használnak és esetleg nem látják a vizuális kontextust.

Másodlagos előnyök: Minden felhasználó számára javítja a használhatóságot, csökkenti a kitöltési hibák számát, és gyorsabbá teszi az űrlapok használatát, mert mindenki pontosan tudja, mit kell tennie.

Tesztelés

  1. Képernyőolvasó teszt: Használj képernyőolvasót (NVDA, JAWS, VoiceOver) és navigálj végig az űrlapon. Ellenőrizd, hogy minden mező címkéje és utasításai felolvasásra kerülnek
  2. Vizuális vizsgálat: Tekintsd át az űrlapot vizuálisan és győződj meg róla, hogy minden beviteli mező rendelkezik látható címkével vagy utasítással
  3. Kód ellenőrzés: Vizsgáld meg a HTML kódot, hogy minden input elem rendelkezik-e megfelelő label elemmel vagy aria-label/aria-labelledby attribútumokkal
  4. Fókusz teszt: Tab billentyűvel navigálj a mezők között és ellenőrizd, hogy minden fókuszált elem esetén egyértelmű, mit kell csinálni
  5. Felhasználói teszt: Kérj meg olyan embereket, akik nem ismerik az oldalt, hogy töltsék ki az űrlapot és figyeld meg, hol akadnak el vagy mit értelmeznek rosszul

Jó gyakorlatok

1. Explicit címkék minden űrlapmezőhöz

<!-- REGISZTRÁCIÓS ŰRLAP - Minden mezőhöz világos címke -->
<form class="registration-form">
  <div class="form-header">
    <h2>Új fiók létrehozása</h2>
    <p>Minden csillaggal (*) jelölt mező kitöltése kötelező.</p>
  </div>
  
  <div class="form-group">
    <!-- Explicit label kapcsolat a for és id attribútumokkal -->
    <label for="reg-username">
      Felhasználónév 
      <span class="required" aria-label="kötelező mező">*</span>
    </label>
    <input 
      type="text" 
      id="reg-username" 
      name="username" 
      required
      aria-describedby="username-help"
      autocomplete="username"
    >
    <div id="username-help" class="field-help">
      3-20 karakter, csak betűk és számok használhatók
    </div>
  </div>
  
  <div class="form-group">
    <label for="reg-email">
      E-mail cím 
      <span class="required" aria-label="kötelező mező">*</span>
    </label>
    <input 
      type="email" 
      id="reg-email" 
      name="email" 
      required
      aria-describedby="email-help"
      autocomplete="email"
    >
    <div id="email-help" class="field-help">
      Valós e-mail címét adja meg az aktiváláshoz (pl. nev@domain.hu)
    </div>
  </div>
  
  <div class="form-group">
    <label for="reg-password">
      Jelszó 
      <span class="required" aria-label="kötelező mező">*</span>
    </label>
    <input 
      type="password" 
      id="reg-password" 
      name="password" 
      required
      aria-describedby="password-requirements"
      autocomplete="new-password"
    >
    <div id="password-requirements" class="field-help">
      <strong>Jelszó követelmények:</strong>
      <ul>
        <li>Legalább 8 karakter hosszú</li>
        <li>Tartalmaz legalább egy nagybetűt</li>
        <li>Tartalmaz legalább egy számot</li>
        <li>Tartalmaz legalább egy speciális karaktert (!@#$%^&*)</li>
      </ul>
    </div>
  </div>
  
  <div class="form-group">
    <label for="reg-password-confirm">
      Jelszó megerősítése 
      <span class="required" aria-label="kötelező mező">*</span>
    </label>
    <input 
      type="password" 
      id="reg-password-confirm" 
      name="password-confirm" 
      required
      aria-describedby="password-confirm-help"
      autocomplete="new-password"
    >
    <div id="password-confirm-help" class="field-help">
      Írja be újra ugyanazt a jelszót a megerősítéshez
    </div>
  </div>
  
  <div class="form-group">
    <label for="birth-date">
      Születési dátum 
      <span class="required" aria-label="kötelező mező">*</span>
    </label>
    <input 
      type="date" 
      id="birth-date" 
      name="birth-date" 
      required
      aria-describedby="birth-date-help"
      max="2006-01-01"
    >
    <div id="birth-date-help" class="field-help">
      Legalább 18 évesnek kell lennie a regisztrációhoz (ÉÉÉÉ-HH-NN formátum)
    </div>
  </div>
  
  <div class="form-group">
    <fieldset>
      <legend>Nem <span class="required" aria-label="kötelező választás">*</span></legend>
      <div class="radio-group">
        <input type="radio" id="gender-male" name="gender" value="male" required>
        <label for="gender-male">Férfi</label>
      </div>
      <div class="radio-group">
        <input type="radio" id="gender-female" name="gender" value="female" required>
        <label for="gender-female">Nő</label>
      </div>
      <div class="radio-group">
        <input type="radio" id="gender-other" name="gender" value="other" required>
        <label for="gender-other">Egyéb/Nem kívánom megadni</label>
      </div>
    </fieldset>
  </div>
  
  <div class="form-group">
    <label for="phone">Telefonszám (opcionális)</label>
    <input 
      type="tel" 
      id="phone" 
      name="phone"
      aria-describedby="phone-help"
      autocomplete="tel"
      placeholder="+36 1 234 5678"
    >
    <div id="phone-help" class="field-help">
      Opcionális mező. Magyar vagy nemzetközi formátumban (+36 1 234 5678 vagy 06 1 234 5678)
    </div>
  </div>
  
  <div class="form-group">
    <fieldset>
      <legend>Adatvédelmi hozzájárulások</legend>
      <div class="checkbox-group">
        <input type="checkbox" id="terms" name="terms" required>
        <label for="terms">
          Elolvastam és elfogadom a 
          <a href="/felhasznalasi-feltetelek" target="_blank" rel="noopener">felhasználási feltételeket</a> 
          <span class="required" aria-label="kötelező">*</span>
        </label>
      </div>
      
      <div class="checkbox-group">
        <input type="checkbox" id="privacy" name="privacy" required>
        <label for="privacy">
          Elfogadom az 
          <a href="/adatvedelem" target="_blank" rel="noopener">adatvédelmi tájékoztatót</a> 
          <span class="required" aria-label="kötelező">*</span>
        </label>
      </div>
      
      <div class="checkbox-group">
        <input type="checkbox" id="newsletter" name="newsletter">
        <label for="newsletter">
          Szeretnék hírlevelet kapni (opcionális)
        </label>
      </div>
    </fieldset>
  </div>
  
  <div class="form-actions">
    <button type="submit" class="btn-primary">Fiók létrehozása</button>
    <button type="reset" class="btn-secondary">Mezők törlése</button>
  </div>
</form>

<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;
  padding-bottom: 1rem;
  border-bottom: 2px solid #f8f9fa;
}

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

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

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

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

.form-group input, .form-group select {
  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, .form-group select:focus {
  outline: none;
  border-color: #007bff;
  box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}

.required {
  color: #dc3545;
  font-weight: bold;
}

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

.field-help ul {
  margin: 0.5rem 0;
  padding-left: 1.2rem;
}

.field-help li {
  margin-bottom: 0.25rem;
}

fieldset {
  border: 2px solid #e9ecef;
  border-radius: 6px;
  padding: 1rem;
  margin: 0;
}

legend {
  font-weight: 600;
  color: #495057;
  padding: 0 0.5rem;
}

.radio-group, .checkbox-group {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.radio-group input[type="radio"],
.checkbox-group input[type="checkbox"] {
  width: auto;
  margin-top: 0.25rem;
}

.radio-group label,
.checkbox-group label {
  margin-bottom: 0;
  font-weight: normal;
  cursor: pointer;
  line-height: 1.4;
}

.form-actions {
  display: flex;
  gap: 1rem;
  margin-top: 2rem;
  padding-top: 1rem;
  border-top: 2px solid #f8f9fa;
}

.btn-primary, .btn-secondary {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
  transition: all 0.2s ease;
}

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

.btn-primary:hover, .btn-primary:focus {
  background: #218838;
}

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

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

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

Magyarázat: Minden beviteli mező rendelkezik explicit label elemmel, amely a for attribútummal kapcsolódik az input id-jához. További utasítások az aria-describedby attribútummal vannak kapcsolva.

2. Keresési és szűrési felületek megfelelő címkézéssel

<!-- FEJLETT KERESÉSI FELÜLET - Világos útmutatásokkal -->
<div class="search-interface">
  <div class="search-header">
    <h2>Termékkeresés</h2>
    <p>Használja az alábbi mezőket a termékek szűréséhez és kereséséhez.</p>
  </div>
  
  <form class="search-form" role="search">
    <!-- Fő keresőmező -->
    <div class="search-group main-search">
      <label for="search-query" class="search-label">
        Keresett termék neve vagy leírása
      </label>
      <div class="search-input-container">
        <input 
          type="search" 
          id="search-query" 
          name="q"
          aria-describedby="search-help"
          placeholder="Pl. laptop, telefon, fülhallgató..."
          autocomplete="off"
        >
        <button type="submit" class="search-button" aria-label="Keresés indítása">
          <svg aria-hidden="true" class="search-icon">
            <use href="#search-icon"></use>
          </svg>
          Keresés
        </button>
      </div>
      <div id="search-help" class="field-help">
        Írja be a keresett termék nevét, márkáját vagy bármilyen kulcsszót
      </div>
    </div>
    
    <!-- Szűrési opciók -->
    <div class="filters-section">
      <h3>Szűrési beállítások</h3>
      
      <div class="filter-row">
        <div class="filter-group">
          <label for="category-filter">Kategória</label>
          <select id="category-filter" name="category" aria-describedby="category-help">
            <option value="">Minden kategória</option>
            <option value="electronics">Elektronika</option>
            <option value="clothing">Ruházat</option>
            <option value="home">Otthon és kert</option>
            <option value="sports">Sport és szabadidő</option>
            <option value="books">Könyvek</option>
          </select>
          <div id="category-help" class="field-help">
            Válasszon kategóriát a keresés szűkítéséhez
          </div>
        </div>
        
        <div class="filter-group">
          <label for="brand-filter">Márka</label>
          <select id="brand-filter" name="brand" aria-describedby="brand-help">
            <option value="">Minden márka</option>
            <option value="apple">Apple</option>
            <option value="samsung">Samsung</option>
            <option value="sony">Sony</option>
            <option value="lg">LG</option>
            <option value="nike">Nike</option>
          </select>
          <div id="brand-help" class="field-help">
            Szűrés márka szerint (opcionális)
          </div>
        </div>
      </div>
      
      <div class="filter-row">
        <div class="filter-group price-range">
          <fieldset>
            <legend>Ár tartomány (Ft)</legend>
            <div class="price-inputs">
              <div class="price-input-group">
                <label for="min-price">Minimum ár</label>
                <input 
                  type="number" 
                  id="min-price" 
                  name="min-price"
                  min="0"
                  step="1000"
                  aria-describedby="min-price-help"
                  placeholder="0"
                >
                <div id="min-price-help" class="field-help">
                  Legalacsonyabb ár forintban (pl. 10000)
                </div>
              </div>
              
              <div class="price-separator">–</div>
              
              <div class="price-input-group">
                <label for="max-price">Maximum ár</label>
                <input 
                  type="number" 
                  id="max-price" 
                  name="max-price"
                  min="0"
                  step="1000"
                  aria-describedby="max-price-help"
                  placeholder="1000000"
                >
                <div id="max-price-help" class="field-help">
                  Legmagasabb ár forintban (pl. 500000)
                </div>
              </div>
            </div>
          </fieldset>
        </div>
      </div>
      
      <div class="filter-row">
        <fieldset class="rating-filter">
          <legend>Minimum értékelés</legend>
          <div class="rating-options">
            <input type="radio" id="rating-any" name="rating" value="" checked>
            <label for="rating-any">Bármely értékelés</label>
            
            <input type="radio" id="rating-4" name="rating" value="4">
            <label for="rating-4">4+ csillag</label>
            
            <input type="radio" id="rating-3" name="rating" value="3">
            <label for="rating-3">3+ csillag</label>
          </div>
        </fieldset>
      </div>
      
      <div class="filter-row">
        <fieldset class="availability-filter">
          <legend>Elérhetőség</legend>
          <div class="checkbox-options">
            <div class="checkbox-group">
              <input type="checkbox" id="in-stock" name="availability" value="in-stock">
              <label for="in-stock">Raktáron van</label>
            </div>
            
            <div class="checkbox-group">
              <input type="checkbox" id="free-shipping" name="shipping" value="free">
              <label for="free-shipping">Ingyenes szállítás</label>
            </div>
            
            <div class="checkbox-group">
              <input type="checkbox" id="on-sale" name="promotion" value="sale">
              <label for="on-sale">Akciós termékek</label>
            </div>
          </div>
        </fieldset>
      </div>
    </div>
    
    <div class="search-actions">
      <button type="submit" class="btn-search">Szűrt keresés indítása</button>
      <button type="reset" class="btn-clear">Szűrők törlése</button>
      <button type="button" class="btn-save" aria-describedby="save-help">
        Keresés mentése
      </button>
      <div id="save-help" class="field-help">
        Mentse el ezt a keresést későbbi használatra
      </div>
    </div>
  </form>
</div>

<!-- SVG ikonok -->
<svg style="display: none;">
  <defs>
    <symbol id="search-icon" viewBox="0 0 24 24">
      <circle cx="11" cy="11" r="8"></circle>
      <path d="m21 21-4.35-4.35"></path>
    </symbol>
  </defs>
</svg>

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

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

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

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

.main-search {
  margin-bottom: 2rem;
  padding-bottom: 2rem;
  border-bottom: 2px solid #f8f9fa;
}

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

.search-input-container {
  display: flex;
  gap: 0.5rem;
}

.search-input-container input {
  flex: 1;
  padding: 0.75rem;
  border: 2px solid #ced4da;
  border-radius: 4px 0 0 4px;
  font-size: 1rem;
}

.search-button {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
  font-weight: 600;
}

.search-icon {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}

.filters-section h3 {
  margin: 0 0 1.5rem 0;
  color: #495057;
  border-bottom: 2px solid #007bff;
  padding-bottom: 0.5rem;
}

.filter-row {
  display: flex;
  gap: 2rem;
  margin-bottom: 2rem;
  flex-wrap: wrap;
}

.filter-group {
  flex: 1;
  min-width: 200px;
}

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

.filter-group select, .filter-group input {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid #ced4da;
  border-radius: 4px;
}

.price-range {
  flex: 2;
}

.price-inputs {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.price-input-group {
  flex: 1;
}

.price-separator {
  font-weight: bold;
  color: #6c757d;
  padding: 0 0.5rem;
}

.rating-filter, .availability-filter {
  border: 1px solid #e9ecef;
  border-radius: 4px;
  padding: 1rem;
  margin: 0;
}

.rating-filter legend, .availability-filter legend {
  font-weight: 600;
  color: #495057;
  padding: 0 0.5rem;
}

.rating-options {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.rating-options input[type="radio"] {
  width: auto;
  margin-right: 0.25rem;
}

.checkbox-options {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.checkbox-group {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.checkbox-group input[type="checkbox"] {
  width: auto;
}

.search-actions {
  display: flex;
  gap: 1rem;
  align-items: center;
  justify-content: center;
  margin-top: 2rem;
  padding-top: 2rem;
  border-top: 2px solid #f8f9fa;
  flex-wrap: wrap;
}

.btn-search, .btn-clear, .btn-save {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 600;
}

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

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

.btn-save {
  background: #17a2b8;
  color: white;
}

@media (max-width: 768px) {
  .search-interface {
    margin: 1rem;
    padding: 1rem;
  }
  
  .filter-row {
    flex-direction: column;
    gap: 1rem;
  }
  
  .search-input-container {
    flex-direction: column;
  }
  
  .search-input-container input {
    border-radius: 4px;
  }
  
  .search-button {
    border-radius: 4px;
  }
  
  .search-actions {
    flex-direction: column;
  }
}
</style>

Magyarázat: A keresési felület minden vezérlője világos címkékkel és útmutatásokkal rendelkezik. A csoportosított elemek fieldset és legend használatával vannak strukturálva.

3. Fájlfeltöltés és interaktív elemek címkézése

<!-- FÁJLFELTÖLTÉSI FELÜLET - Részletes útmutatásokkal -->
<div class="file-upload-section">
  <h2>Dokumentumok feltöltése</h2>
  
  <form class="upload-form" enctype="multipart/form-data">
    <div class="upload-group">
      <label for="profile-photo" class="upload-label">
        Profilkép feltöltése
      </label>
      <input 
        type="file" 
        id="profile-photo" 
        name="profile-photo"
        accept="image/jpeg,image/png,image/gif"
        aria-describedby="profile-photo-help"
      >
      <div id="profile-photo-help" class="field-help">
        <strong>Támogatott formátumok:</strong> JPG, PNG, GIF<br>
        <strong>Maximum méret:</strong> 5 MB<br>
        <strong>Ajánlott felbontás:</strong> 400x400 pixel
      </div>
    </div>
    
    <div class="upload-group">
      <label for="resume-file" class="upload-label">
        Önéletrajz feltöltése 
        <span class="required" aria-label="kötelező">*</span>
      </label>
      <input 
        type="file" 
        id="resume-file" 
        name="resume"
        accept=".pdf,.doc,.docx"
        required
        aria-describedby="resume-help"
      >
      <div id="resume-help" class="field-help">
        <strong>Kötelező mező.</strong> Támogatott formátumok: PDF, DOC, DOCX<br>
        Maximum méret: 10 MB
      </div>
    </div>
    
    <div class="upload-group">
      <fieldset>
        <legend>Portfólió dokumentumok (opcionális)</legend>
        <div class="multiple-upload">
          <label for="portfolio-files" class="upload-label">
            Válasszon egy vagy több fájlt
          </label>
          <input 
            type="file" 
            id="portfolio-files" 
            name="portfolio[]"
            multiple
            accept=".pdf,.jpg,.jpeg,.png,.doc,.docx"
            aria-describedby="portfolio-help"
          >
          <div id="portfolio-help" class="field-help">
            Több fájl egyidejű kiválasztása lehetséges<br>
            Támogatott formátumok: PDF, JPG, PNG, DOC, DOCX<br>
            Fájlonként maximum 15 MB, összesen maximum 50 MB
          </div>
        </div>
      </fieldset>
    </div>
    
    <!-- Drag and drop terület -->
    <div class="drag-drop-area" 
         role="button" 
         tabindex="0"
         aria-labelledby="drag-drop-label"
         aria-describedby="drag-drop-instructions"
         ondrop="handleDrop(event)" 
         ondragover="handleDragOver(event)"
         ondragenter="handleDragEnter(event)"
         ondragleave="handleDragLeave(event)"
         onkeydown="handleKeyboardDrop(event)">
      
      <div id="drag-drop-label" class="drag-drop-label">
        Húzza ide a fájlokat vagy kattintson a tallózáshoz
      </div>
      
      <div id="drag-drop-instructions" class="drag-drop-instructions">
        Támogatott formátumok: PDF, DOC, DOCX, JPG, PNG<br>
        Maximum 10 fájl, fájlonként maximum 15 MB
      </div>
      
      <button type="button" class="browse-button" onclick="triggerFileSelect()">
        Fájlok tallózása
      </button>
      
      <input 
        type="file" 
        id="drag-drop-input" 
        name="additional-files[]"
        multiple
        accept=".pdf,.doc,.docx,.jpg,.jpeg,.png"
        style="display: none;"
        aria-label="További fájlok kiválasztása"
      >
    </div>
    
    <!-- Feltöltött fájlok listája -->
    <div id="uploaded-files-section" class="uploaded-files" style="display: none;">
      <h3>Feltöltött fájlok</h3>
      <ul id="uploaded-files-list" role="list" aria-label="Feltöltött fájlok listája">
        <!-- JavaScript tölti fel dinamikusan -->
      </ul>
    </div>
    
    <div class="form-actions">
      <button type="submit" class="btn-upload">Dokumentumok feltöltése</button>
      <button type="reset" class="btn-clear">Minden törlése</button>
    </div>
  </form>
</div>

<script>
let uploadedFiles = [];

function handleDrop(event) {
  event.preventDefault();
  const files = event.dataTransfer.files;
  processFiles(files);
  updateDragDropVisuals(false);
}

function handleDragOver(event) {
  event.preventDefault();
}

function handleDragEnter(event) {
  event.preventDefault();
  updateDragDropVisuals(true);
}

function handleDragLeave(event) {
  event.preventDefault();
  if (!event.relatedTarget || !event.currentTarget.contains(event.relatedTarget)) {
    updateDragDropVisuals(false);
  }
}

function handleKeyboardDrop(event) {
  if (event.key === 'Enter' || event.key === ' ') {
    event.preventDefault();
    triggerFileSelect();
  }
}

function triggerFileSelect() {
  document.getElementById('drag-drop-input').click();
}

function updateDragDropVisuals(isDragOver) {
  const dragDropArea = document.querySelector('.drag-drop-area');
  if (isDragOver) {
    dragDropArea.classList.add('drag-over');
  } else {
    dragDropArea.classList.remove('drag-over');
  }
}

function processFiles(files) {
  const fileArray = Array.from(files);
  const validFiles = fileArray.filter(validateFile);
  
  validFiles.forEach(file => {
    uploadedFiles.push(file);
  });
  
  updateUploadedFilesList();
  announceFileUpload(validFiles.length, fileArray.length - validFiles.length);
}

function validateFile(file) {
  const maxSize = 15 * 1024 * 1024; // 15 MB
  const allowedTypes = ['application/pdf', 'application/msword', 
                       'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                       'image/jpeg', 'image/png'];
  
  if (file.size > maxSize) {
    alert(`A fájl "${file.name}" túl nagy. Maximum 15 MB engedélyezett.`);
    return false;
  }
  
  if (!allowedTypes.includes(file.type)) {
    alert(`A fájl "${file.name}" formátuma nem támogatott.`);
    return false;
  }
  
  return true;
}

function updateUploadedFilesList() {
  const section = document.getElementById('uploaded-files-section');
  const list = document.getElementById('uploaded-files-list');
  
  if (uploadedFiles.length === 0) {
    section.style.display = 'none';
    return;
  }
  
  section.style.display = 'block';
  list.innerHTML = '';
  
  uploadedFiles.forEach((file, index) => {
    const li = document.createElement('li');
    li.className = 'uploaded-file-item';
    li.innerHTML = `
      <div class="file-info">
        <span class="file-name">${file.name}</span>
        <span class="file-size">(${formatFileSize(file.size)})</span>
      </div>
      <button type="button" 
              class="remove-file-btn" 
              onclick="removeFile(${index})"
              aria-label="Fájl eltávolítása: ${file.name}">
        Eltávolítás
      </button>
    `;
    list.appendChild(li);
  });
}

function removeFile(index) {
  const fileName = uploadedFiles[index].name;
  uploadedFiles.splice(index, 1);
  updateUploadedFilesList();
  announceToScreenReader(`Fájl eltávolítva: ${fileName}`);
}

function formatFileSize(bytes) {
  if (bytes === 0) return '0 Bytes';
  const k = 1024;
  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

function announceFileUpload(validCount, invalidCount) {
  let message = `${validCount} fájl sikeresen hozzáadva`;
  if (invalidCount > 0) {
    message += `, ${invalidCount} fájl elutasítva`;
  }
  announceToScreenReader(message);
}

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);
}

// Event listeners
document.getElementById('drag-drop-input').addEventListener('change', function(event) {
  processFiles(event.target.files);
});
</script>

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

.upload-group {
  margin-bottom: 2rem;
  padding: 1.5rem;
  border: 1px solid #e9ecef;
  border-radius: 6px;
  background: #f8f9fa;
}

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

.upload-group input[type="file"] {
  width: 100%;
  padding: 0.5rem;
  border: 2px solid #ced4da;
  border-radius: 4px;
  background: white;
}

.drag-drop-area {
  border: 3px dashed #ced4da;
  border-radius: 8px;
  padding: 3rem 2rem;
  text-align: center;
  margin: 2rem 0;
  cursor: pointer;
  transition: all 0.3s ease;
  background: #f8f9fa;
}

.drag-drop-area:hover, .drag-drop-area:focus {
  border-color: #007bff;
  background: #e3f2fd;
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

.drag-drop-area.drag-over {
  border-color: #28a745;
  background: #d4edda;
}

.drag-drop-label {
  font-size: 1.2em;
  font-weight: 600;
  color: #495057;
  margin-bottom: 1rem;
}

.drag-drop-instructions {
  color: #6c757d;
  margin-bottom: 1.5rem;
  line-height: 1.4;
}

.browse-button {
  background: #007bff;
  color: white;
  border: none;
  padding: 0.75rem 1.5rem;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 600;
}

.uploaded-files {
  margin-top: 2rem;
  padding: 1.5rem;
  border: 1px solid #e9ecef;
  border-radius: 6px;
  background: #f8f9fa;
}

.uploaded-files h3 {
  margin: 0 0 1rem 0;
  color: #495057;
}

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

.uploaded-file-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem;
  margin-bottom: 0.5rem;
  background: white;
  border: 1px solid #dee2e6;
  border-radius: 4px;
}

.file-info {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.file-name {
  font-weight: 600;
  color: #495057;
}

.file-size {
  font-size: 0.9em;
  color: #6c757d;
}

.remove-file-btn {
  background: #dc3545;
  color: white;
  border: none;
  padding: 0.25rem 0.75rem;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9em;
}

.required {
  color: #dc3545;
  font-weight: bold;
}

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

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

.btn-upload, .btn-clear {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 600;
}

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

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

.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) {
  .file-upload-section {
    margin: 1rem;
    padding: 1rem;
  }
  
  .uploaded-file-item {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }
  
  .form-actions {
    flex-direction: column;
  }
}
</style>

Magyarázat: A fájlfeltöltési felület minden elemének van világos címkéje és részletes útmutatása. A drag-and-drop terület is megfelelően címkézett és billentyűzetről is elérhető.

Rossz gyakorlatok

Hiányzó vagy nem megfelelő címkék

<!-- HIBÁS: Nincs label elem -->
<form>
  <!-- HIBA: Nincs címke, csak placeholder -->
  <input type="text" placeholder="Név">
  
  <!-- HIBA: Nincs címke, csak vizuális jelzés -->
  <span>E-mail:</span>
  <input type="email">
  
  <!-- HIBA: Label nincs kapcsolva az inputhoz -->
  <label>Telefonszám</label>
  <input type="tel" id="phone"> <!-- Nincs for attribútum a labelben -->
</form>

<!-- HIBÁS: Placeholder mint egyetlen útmutatás -->
<form>
  <!-- HIBA: Csak placeholder, nincs állandó címke -->
  <input type="password" placeholder="Jelszó (min. 8 karakter, 1 nagybetű, 1 szám)">
  
  <!-- HIBA: Placeholder eltűnik beírás közben -->
  <input type="date" placeholder="Születési dátum (ÉÉÉÉ-HH-NN)">
</form>

<!-- HIBÁS: Nem informatív címkék -->
<form>
  <label for="input1">Mező</label>
  <input id="input1" type="text">
  
  <label for="input2">Adat</label>
  <input id="input2" type="text">
  
  <label for="input3">Információ</label>
  <input id="input3" type="text">
</form>

Probléma: A képernyőolvasók nem tudják megmondani a felhasználóknak, mit kell beírniuk, mert nincsenek megfelelően programozott címkék.

Csak vizuális útmutatások szöveg nélkül

<!-- HIBÁS: Csak színes jelzések -->
<form>
  <label for="required-field1">Név</label>
  <!-- HIBA: Csak piros * jelzi a kötelező mezőt -->
  <input id="required-field1" type="text" style="border-left: 5px solid red;">
  
  <label for="optional-field1">Megjegyzés</label>
  <!-- HIBA: Csak zöld szegély jelzi az opcionális mezőt -->
  <input id="optional-field1" type="text" style="border-left: 5px solid green;">
</form>

<!-- HIBÁS: Csak ikonok szöveg nélkül -->
<form>
  <div>
    👤 <input type="text" placeholder="..."> <!-- Mi ez? -->
  </div>
  <div>
    📧 <input type="email" placeholder="..."> <!-- Mi ez? -->
  </div>
  <div>
    🔒 <input type="password" placeholder="..."> <!-- Mi ez? -->
  </div>
</form>

<!-- HIBÁS: Csak pozíció alapú útmutatás -->
<form>
  <div class="form-row">
    <!-- HIBA: Nincs címke, csak pozíció alapján kellene tudni -->
    <input type="text"> <!-- Keresztnév? Vezetéknév? -->
    <input type="text"> <!-- Vezetéknév? Keresztnév? -->
  </div>
  <p>Első mező: vezetéknév, második mező: keresztnév</p>
</form>

Probléma: A csak vizuális útmutatások nem érhetők el képernyőolvasókkal, és a színvak felhasználók sem érzékelik őket.

Hiányzó utasítások komplex űrlapmezőkhöz

<!-- HIBÁS: Komplex mezők útmutatás nélkül -->
<form>
  <!-- HIBA: Nincs formátum útmutatás -->
  <label for="credit-card">Bankkártya száma</label>
  <input id="credit-card" type="text">
  
  <!-- HIBA: Nincs magyarázat a CVC kódról -->
  <label for="cvv">CVC</label>
  <input id="cvv" type="text">
  
  <!-- HIBA: Nincs dátum formátum megadva -->
  <label for="expiry">Lejárat</label>
  <input id="expiry" type="text">
</form>

<!-- HIBÁS: Jelszó követelmények rejtettek -->
<form>
  <label for="new-password">Új jelszó</label>
  <input id="new-password" type="password">
  <!-- HIBA: Követelmények csak hiba után jelennek meg -->
</form>

<!-- HIBÁS: Csoportosított mezők címke nélkül -->
<form>
  <!-- HIBA: Nincs fieldset és legend -->
  <input type="radio" id="small" name="size" value="small">
  <label for="small">Kicsi</label>
  
  <input type="radio" id="medium" name="size" value="medium">
  <label for="medium">Közepes</label>
  
  <input type="radio" id="large" name="size" value="large">
  <label for="large">Nagy</label>
  
  <!-- Mi ez? Méret minek? Ruha? Pizza? -->
</form>

Probléma: A felhasználók nem tudják, milyen formátumban vagy mit kell megadniuk, ami hibás kitöltéshez és frusztrációhoz vezet.

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