omeryanbas.com

Ömer Yanbaş

Genel Müdür, Ticofab Yazılım

WebYöntem

Neredeyse her sitenin yaptığı iki erişilebilirlik hatası

Başlık seviyelerinin tasarıma göre seçilmesi ve görünen metni gizleyen aria-label. İkisi de klavye ve ses kullanıcısını engeller, ikisinin de çözümü ucuz.

Site otomatik denetimden iyi bir puanla geçer. Kontrast yeterlidir, her görselin alt metni, her input'un label'ı vardır. Sonra biri sayfayı ekran okuyucuyla açar ve sayfanın içindekiler listesi anlamsız çıkar; sesle gezen biri butonun üstünde yazan kelimeyi söyler ve hiçbir şey olmaz. Bunun büyük kısmını iki hata üretir ve ikisi de ekranda görünmez: tasarıma göre seçilmiş başlık seviyeleri ve kontrolün üstünde yazanı içermeyen bir aria-label.

Gerçekte ne oluyor

Başlık bir içindekiler listesidir, punto ölçeği değil

Ekran okuyucu sayfadaki bütün başlıkları liste halinde verir ve uzun bir sayfada çoğu kullanıcı o listeyle gezer. Önce listeyi okur, bir bölüm seçer, oraya atlar. Seviyeler bu listenin iskeletidir: h2 sayfanın bir bölümü, h3 o bölümün bir parçasıdır. h2'den h4'e atlamak, açılışı kaybolmuş bir bölüm gibi okunur.

Seviyeler neredeyse hiç bilerek bozulmaz. Bir başlığın daha küçük görünmesi gerektiği için bozulur. Kenar sütunundaki başlık h4 yapılır çünkü h4 istenen puntoda stillenmiştir; karttaki başlık h2 yapılır çünkü tasarımda büyük duruyordur. Sayfa doğru görünür, içindekiler listesi karışır.

Sorunun diğer yarısı sıra. İki sütunlu yerleşimde dar sütun çoğu zaman markup'ta önce durur, çünkü onu sola almanın en kolay yolu buydu. Ekranda ikincil bir kutu gibi okunur. Dökümanda ise ana başlıktan önce gelir, yani liste bir kenar notuyla açılır.

İkisi de aynı sebepten gözden kaçar: ekipteki hiç kimse sayfayı başlık listesinden okumaz. Tasarım gözden geçirmesinde ekrana bakılır, kod incelemesinde bileşenin kendi dosyasına bakılır, ikisinde de sayfanın tamamının hangi sırayla okunduğu görünmez. Hata bir bileşende değil, bileşenlerin bir araya geldiği yerde durur.

Erişilebilir ad, görünen metni içermek zorunda

aria-label kontrole açıklama eklemez. Kontrolün adını tamamen değiştirir. Butonun içine yazdığınız metin, yardımcı teknoloji açısından artık o kontrolün adı değildir.

Bu, ses kontrolü kullananlar için doğrudan bir engel. Ekrana bakarlar, kontrolün üstünde yazanı okurlar ve söylerler. Yazılım söyleneni erişilebilir adla eşleştirir. Üstünde Gönder yazan ama aria-label="İletişim formunu gönder" taşıyan bir butonda eşleşme olmaz, komut sessizce düşer. Kullanıcı butonu görür ama çalıştıramaz.

Ekranı büyüterek ekran okuyucu kullanan biri için de kafa karıştırıcıdır: hem görünen metin hem seslendirilen metin ortadadır ve birbirini tutmaz. Bunu kapsayan kural kısa: erişilebilir ad, görünen metni aynı sırayla içermelidir.

Nasıl görülür

Başlık ağacını konsoldan bastırın. On saniye sürer ve markup'ı okumaktan daha dürüsttür:

let previous = 0;
for (const h of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
  const level = Number(h.tagName[1]);
  const flag = previous && level > previous + 1 ? '  [seviye atlandı]' : '';
  console.log(`${'. '.repeat(level - 1)}h${level} ${h.textContent.trim().slice(0, 50)}${flag}`);
  previous = level;
}

Çıktıyı içindekiler listesi gibi okuyun. Cümle cümle okunduğunda anlam vermiyorsa, başlıklarla gezen biri için de anlam vermiyor demektir. Tarayıcı geliştirici araçlarındaki erişilebilirlik ağacı paneli aynı şeyi hesaplanmış adlarla birlikte gösterir; ikinci kontrol için de orası işinize yarar.

Etiketler için yazılanla seslendirileni karşılaştırın:

for (const el of document.querySelectorAll('[aria-label]')) {
  const visible = el.innerText.trim().toLowerCase();
  const name = el.getAttribute('aria-label').trim().toLowerCase();
  if (visible && !name.includes(visible)) {
    console.warn('label in name:', { visible, name, el });
  }
}

Hiç görünen metni olmayan kontroller, mesela sadece ikonlu bir buton, burada temiz görünür ve ayrı bir kontrol ister: adı olmalı, ikonun da tooltip'i olmalı ki sesle gezen kullanıcının söyleyecek bir kelimesi olsun.

İkinci parçacığı bir kere çalıştırıp bitirmeyin. İki uyarıyı ayırt etmek gerekiyor: görünen metnin adın içinde hiç geçmemesi gerçek hata, kelimelerin sırasının kayması ise sınırda bir durum. Sesle gezen kullanıcı cümlenin tamamını değil, gördüğü ilk birkaç kelimeyi söyler; bu yüzden adın başındaki kelimeler butonun üstündeki kelimelerle aynı olmalı.

Otomatik denetimin build içinde yeri var, çünkü eksik adları ve atlanan seviyeleri insandan hızlı bulur. İçindekiler listesinin içerikle uyuşup uyuşmadığını söyleyemez; aynı sınır responsive images denetiminde de karşınıza çıkar. Araç nereye bakacağınızı söyler, neyin doğru olduğuna siz karar verirsiniz.

Çözüm

Markup'ı okuma sırasıyla yazın, yerleşimi sonra kurun. Düzelttiğim başlık sırası hatalarının neredeyse hepsi, üstüne CSS yamanmış bir markup sırası hatasıydı ve tamiri göründüğünden küçük:

<main class="page">
  <h1>Fiyatlar</h1>
  <section class="plans">
    <h2>Paketler</h2>
    <h3>Ekip</h3>
  </section>
  <aside class="notes">
    <h2>Pakete dahil olanlar</h2>
  </aside>
</main>
.page {
  display: grid;
  grid-template-columns: 16rem 1fr;
  grid-template-areas:
    "title title"
    "notes plans";
}
.page > h1 { grid-area: title; }
.plans { grid-area: plans; }
.notes { grid-area: notes; }

/* punto bir stil kararı, seviye bir yapı kararı */
.notes h2 { font-size: 1rem; font-weight: 600; }

Kenar sütunu solda çizilir, ana bölümden sonra okunur. Grid alanları ve flex sıralaması çizimi değiştirir, dökümanı değiştirmez; klavye de ekran okuyucu da yazdığınız sırayı izlemeye devam eder.

Bunun bir bedeli var ve söylemek gerekiyor: yerleşimi artık iki yerden okuyorsunuz, markup'tan ve grid tanımından. Alan adlarını içerikten türetirseniz, yani sidebar yerine notes gibi isimler kullanırsanız, altı ay sonra dosyayı açan kişi de iki yeri birlikte okuyabilir.

Etiketlerde en kısa doğru cevap çoğu zaman özniteliği silmektir. Butonun içindeki metin zaten iyi bir addı. Gerçekten görünen kelimelerden fazlası lazımsa, görünen kelimeleri başa alın:

<button>Gönder</button>

<button aria-label="Gönder: destek ekibine mesaj">Gönder</button>

<button class="icon-btn" aria-label="Kapat" title="Kapat">
  <svg aria-hidden="true" focusable="false" width="16" height="16"><use href="#x"></use></svg>
</button>

Ortadaki biçim uzlaşma noktası: ekran okuyucuya fazladan bağlam gider, söylenen gönder kelimesi yine eşleşir. İkonlu buton hem ad hem tooltip alır, böylece ad tahmin edilmez, görünür olur.

Aynı dosyadayken bir dakikalık üç iş daha var:

  • Focus halkasını geri koyun. :focus-visible { outline: 2px solid currentColor; outline-offset: 2px; } yeter ve yalnızca klavyeyle gezerken görünür. Üstüne transition koymayın ki tuşa basıldığı anda gelsin, bir tempo sonra değil; sebebi giriş animasyonunun en büyük boyamayı geciktirmesiyle aynı.
  • Her tıklanabilir alanı padding dahil en az 24 x 24 CSS pikseli yapın. Araç çubuğundaki küçük ikon butonları en sık buradan düşer.
  • İlk odaklanılabilir eleman olarak bir skip link ekleyin ve stilini inline style yerine stil dosyanızdan verin; bu aynı zamanda katı bir içerik güvenlik politikasını bozmadan tutar.

Çalıştığını nasıl doğrularsınız

İki konsol parçacığını tekrar çalıştırın. Başlık ağacı boşluksuz bir içindekiler listesi gibi okunur, etiket kontrolü hiçbir şey basmaz:

h1 Fiyatlar
. h2 Paketler
. . h3 Ekip
. h2 Pakete dahil olanlar

Sonra fareyi bırakın. Adres çubuğundan itibaren Tab'a basın: ilk çıkan şey skip link olmalı, her durak görünür olmalı ve sıra gördüğünüz sayfayı izlemeli. Tek başına bu tur, herhangi bir rapordan fazlasını bulur.

Nelere dikkat etmeli

  • aria-label, rolü olmayan elemanlarda, mesela düz bir div veya span üzerinde yok sayılır. Etiketi sarmalayıcıya verdiyseniz hiçbir şey seslendirilmemiştir.
  • Ekran okuyucu yazılımları ile tarayıcılar birkaç ayrıntıda ayrışır. Testi kendi makinenizde kolay olan kombinasyonla değil, kullanıcılarınızın gerçekten kullandığı kombinasyonla yapın.
  • display: none veya visibility: hidden kullanan bir "görsel olarak gizli" sınıfı, metni erişilebilirlik ağacından da siler. Onun yerine clip tabanlı kalıbı kullanın.
  • Bileşen içindeki başlık seviyesi, bileşenin nerede kullanıldığına bağlıdır. Liste sayfasında h3 olan kart başlığı, kart bir seviye derindeki bölüme konunca yanlış olur. Seviyeyi sabitlemek yerine prop olarak geçirin.

Bu hataların ikisi de egzotik değil ve umursamamaktan da doğmuyor. Markup'ı dökümana göre değil bir görsele göre yazmaktan ve aria-label'ı açıklama sanmaktan doğuyorlar. İçindekiler listesini kontrol edin, seslendirilen adın görünen adı içerdiğini kontrol edin; koca bir şikâyet kategorisi kimse yazmadan önce ortadan kalkar.

Sorular ve cevaplar

Başlık seviyesi atlamak gerçekten sorun mu?
Başlıklarla gezen herkes için sorun. Ekran okuyucu kullananların çoğu uzun bir sayfada başlık listesini açar ve sayfayı içindekiler gibi okur. h2'den sonra h4 gelince arada kaybolmuş bir bölüm varmış gibi okunur. Gören kullanıcı aynı bilgiyi punto ve boşluktan aldığı için hata normal testte hiç fark edilmez.
Label in name kuralı nedir?
Bir kontrolün erişilebilir adının, üzerinde görünen metni içermesi gerektiğini söyleyen kural. Ses kontrolü yazılımı kullanıcının söylediğini erişilebilir adla eşleştirir. Üzerinde Gönder yazan ama adı İletişim formunu gönder olan bir butona sesle tıklanamaz. Pratik kural şu: aria-label görünen kelimelerle başlamalı ya da onları içinde barındırmalı.
Başlık sırasını markup'ı taşımadan CSS ile düzeltebilir miyim?
Kutuları taşıyabilirsiniz, okuma sırasını taşıyamazsınız. Grid alanları ve flex sıralaması elemanın nerede çizileceğini değiştirir, döküman sırası aynı kalır. Yardımcı teknolojinin ve klavyenin izlediği şey ise döküman sırasıdır. Bu yüzden başlıkları okunması gereken sırayla yazıp yerleşimi sonra kurun.
Otomatik denetim yeterli mi?
Gerçek sorunların kabaca üçte birini yakalar, bu da her build'de çalıştırmaya değer. Atlanan seviyeyi ve eksik erişilebilir adı bulur; adın anlamlı olup olmadığını veya içindekiler listesinin içerikle uyuşup uyuşmadığını söyleyemez. Yanına bir başlık ağacı ve sayfayı sadece klavyeyle bir kez gezmeyi ekleyin.