Screen Reader ile Test Etmek: NVDA, VoiceOver ve TalkBack Temel Komutları, Accessibility Tree Okumak
Screen reader kullanıcıları sayfayı çoğu zaman heading, landmark ve link listelerinden gezer; bu yüzden test, browse mode ile focus mode farkını bilmek ve tree'den beklenen konuşmayı önceden yazmakla başlar. NVDA, VoiceOver ve TalkBack'in temel komutlarını, WebAIM 2026 anketini, Chromium tree'sinden element listeleri çıkarmayı, Bookstore'a search landmark'ı ve kitap heading'leri eklemeyi ve manuel oturum için bir test script'i üretmeyi anlatıyoruz.
Sürüm notu: Bu yazıdaki örnekler WCAG (Web Content Accessibility Guidelines; web içeriği için accessibility kılavuzu) 2.2, Angular 22.2 (TypeScript 6.0), Node.js 24, Playwright 1.65 (Chromium 158) ve axe-core 4.14 ile denenmiştir; screen reader komutları NVDA (NonVisual Desktop Access; Windows için ücretsiz screen reader) 2026.3, macOS VoiceOver ve TalkBack 9.1+ dokümanlarından alınmıştır.
Önce Tahmin Et
WebAIM'in (Utah State Üniversitesi'ndeki web accessibility kuruluşu) Temmuz-Ağustos 2026'da 1.780 kişiyle yaptığı 11. Screen Reader User Survey'inde şu soru var: uzun bir sayfada bilgi ararken ilk ne yaparsınız? Cevapların yüzde kaçı "heading'lerle gezerim" der? Tahminini tut ve ikinci soruya geç: sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) kataloğunda yirmi kitap var. Screen reader'ın heading listesinde kaç heading görünür?
Bu yazı, serinin gerçek bir screen reader ile test etme yazısı. Bir uyarıyla başlayalım: yazıyı hazırladığım ortamda NVDA, VoiceOver ya da TalkBack ile bir oturum açılamadı. Komutlar resmî dokümanlardan; gösterdiğim çıktıların hepsi Chromium'un accessibility tree'sinden, ve her "heard" sütunu "çalıştırılmadı". Yazının amacı da bu ayrımı netleştirmek: tree'den neyi önceden bilebilirsin, neyi ancak bir screen reader'la duyabilirsin.
Screen Reader Sayfayı Nasıl Gezer?
Gören bir kullanıcı sayfaya bakar ve gözüyle atlar. Screen reader kullanıcısı sayfayı doğrusal bir metin gibi alır ve bu metinde tuşlarla hareket eder. Windows screen reader'larında bunun için iki mod var. Browse mode (JAWS'ta virtual cursor; JAWS, Freedom Scientific'in ücretli screen reader'ı), sayfanın tarayıcıdan alınan bir kopyası üzerinde gezinir: ok tuşları satır satır okur, tek harfler element'ler arasında atlar (H sonraki heading, D sonraki landmark, K sonraki link). Focus mode (JAWS'ta forms mode) ise tuşları sayfaya bırakır: bir metin kutusuna yazarken H harfi heading'e atlamaz, kutuya yazılır. NVDA dokümanına göre bir metin alanına Tab ile ya da tıklayarak gelince NVDA kendiliğinden focus mode'a geçer, NVDA+Space ile elle geçiş yapılır, Escape browse mode'a döndürür.
Bunun geliştiriciye iki sonucu var. Birincisi, browse mode'da kullanıcının "bulunduğu yer" ile DOM'daki (Document Object Model; sayfanın tarayıcıdaki element ağacı) focus her zaman aynı değil; kullanıcı bir heading'i okurken focus hâlâ üstteki butonda olabilir. Yalnız keydown dinleyen bir component, browse mode'daki kullanıcıdan tuş almaz; tuşu screen reader yakalar. Composite widget'larda role'ün önemi buradan geliyor: listbox, tablist, grid gibi role'ler screen reader'a "burada focus mode'a geç ve ok tuşlarını sayfaya bırak" der. İkincisi, kullanıcı sayfayı çoğu zaman baştan sona okumaz, listelerden seçer: heading'ler, landmark'lar, link'ler, form alanları. Bu listelerin ne kadar anlamlı olduğu, sayfanın kullanılabilirliğini doğrudan belirler.
macOS'taki VoiceOver'da böyle iki mod yok; VO (VoiceOver tuşu; varsayılan olarak Control+Option ya da Caps Lock) ile element'ler arasında gezilir, listeler rotor'dan açılır. Mobilde dokunma hareketleri var: TalkBack'te sağa ve sola kaydırma sonraki ve önceki öğe, çift dokunma etkinleştirme; iOS VoiceOver'da da benzer.
Minimum Komut Seti
Bir geliştiricinin test için ezberlemesi gereken komutlar az. Aşağıdaki tablo NVDA 2026.3 User Guide'ından (masaüstü klavye düzeni; NVDA tuşu varsayılan olarak Insert), Deque'nin (accessibility araçları geliştiren şirket) VoiceOver kısayol listesinden ve Google'ın TalkBack 9.1+ hareket listesinden:
| Görev | NVDA (Windows) | VoiceOver (macOS) | TalkBack (Android) |
|---|---|---|---|
| Başlat / kapat | Ctrl+Alt+N / NVDA+Q | Command+F5 | Settings, Accessibility, TalkBack ya da iki ses tuşunu basılı tutmak |
| Konuşmayı durdur | Control | Control | İki parmakla dokunma (duraklat) |
| Sonraki / önceki öğe | Aşağı / yukarı ok (browse mode) | VO+Sağ / Sol ok | Sağa / sola kaydırma |
| Etkinleştir | Enter ya da Space | VO+Space | Çift dokunma |
| Okumaya başla | NVDA+Aşağı ok | VO+A | TalkBack menüsünden |
| Sonraki heading | H (seviye için 1-9) | VO+Command+H | Reading control'de Headings, sonra aşağı kaydırma |
| Sonraki landmark / link / form alanı | D / K / F (E metin alanı, B buton, R radio) | Rotor'dan Landmarks / VO+Command+L / VO+Command+J | Reading control'den |
| Element listesi | NVDA+F7 | VO+U (rotor) | Reading control'ler |
| Mod değiştir | NVDA+Space, Escape | Mod yok | Mod yok |
| Tabloda gezinme | Ctrl+Alt+Oklar | VO+Command+T ile tabloya, sonra VO+oklar | Explore by touch |
Shift ile tek harfli komutlar geriye gider (Shift+H önceki heading). TalkBack'te reading control yukarı-sonra-aşağı hareketiyle (ya da üç parmakla aşağı kaydırma) değiştirilir, TalkBack menüsü aşağı-sonra-sağa hareketiyle ya da üç parmakla dokunarak açılır. Bir oturumda duyduğunu kaydetmek için NVDA'nın Tools menüsündeki Speech Viewer, konuşulan her şeyi metin olarak bir pencereye yazar; test notlarına kopyalamak için en pratik yol bu.
Hangi Screen Reader, Hangi Tarayıcı?
WebAIM'in 2026 anketine göre masaüstünde birincil screen reader JAWS'ta %55,0, NVDA'da %32,9, VoiceOver'da %6,5; "kullandıklarım" sorusunda JAWS %69,2, NVDA %59,3, VoiceOver %42,0 (katılımcıların %70,3'ü birden fazla screen reader kullanıyor). En yaygın kombinasyonlar JAWS + Chrome (%31,2), NVDA + Chrome (%17,3), JAWS + Edge (%17,0) ve NVDA + Firefox (%9,1). Mobilde VoiceOver %72,2, TalkBack %29,5. WebAIM'in kendisi de örneklemin kontrollü olmadığını ve bütün screen reader kullanıcılarını temsil etmeyebileceğini yazıyor; bu sayıları "ne yaygın" sorusunun kabaca cevabı olarak oku.
Geliştirici için pratik seçim: Windows'ta NVDA + Chrome ya da Firefox (NVDA ücretsiz, JAWS ile aynı mod modelini kullanıyor), macOS ve iOS'ta Safari + VoiceOver, Android'de Chrome + TalkBack. JAWS kurumsal ortamlarda ve kamu kurumlarında yaygın; Bookstore'un Kurumsal Sipariş müşterileri (okullar, kütüphaneler) için JAWS + Chrome kombinasyonunu en az bir sürüm döngüsünde bir kez denemek mantıklı. Bir kombinasyonda çalışan şeyin ötekinde de çalışacağını varsayma: tarayıcı tree'yi platform API'sine farklı aktarır, screen reader'lar da onu farklı yorumlar.
Anketin tahmin sorusunun cevabı: uzun bir sayfada bilgi ararken katılımcıların %67,8'i önce heading'lerle geziyor; ardından sayfa içi arama (%12,5), sayfayı okumak (%9,7), link'ler (%6,7) ve landmark'lar (%3,4) geliyor.
Accessibility Tree'den Element Listeleri
NVDA'nın Elements List'i ve VoiceOver'ın rotor'u, tarayıcının verdiği accessibility tree'den liste yapar. Aynı listeleri Chromium'un tree'sinden çıkaran bir script yazdık; Chrome DevTools Protocol'ün Accessibility.getFullAXTree komutunu kullanıyor ve tree'yi kökten dolaşarak sayfanın okuma sırasını koruyor (komutun döndürdüğü dizi bu sırada değil):
// Screen reader'ın accessibility tree'den kurduğu listeleri yazdırır (NVDA'nın Elements List'i ya da
// VoiceOver rotor'u gibi): bir sayfanın landmark, heading, link, buton ve form alanları
// Kullanım: node elements.mjs <catalog|book|cart|statement>
import { chromium } from 'playwright';
import { open } from './lib.mjs';
const landmarkRoles = ['banner', 'navigation', 'main', 'contentinfo', 'search', 'region', 'form', 'complementary'];
const fieldRoles = ['textbox', 'searchbox', 'spinbutton', 'combobox', 'radio', 'checkbox', 'listbox'];
const name = process.argv[2] ?? 'catalog';
const browser = await chromium.launch();
const page = await (await browser.newContext()).newPage();
await open(page, name);
await page.waitForTimeout(300);
const cdp = await page.context().newCDPSession(page);
// Chromium'dan tüm tree; "ignored" node'lar (anlamsız div ve span'ler) screen reader'a gösterilmez
const { nodes } = await cdp.send('Accessibility.getFullAXTree');
// Tree'yi kökten dolaş; böylece listeler sayfanın okuma sırasını korur
const byId = new Map(nodes.map(n => [n.nodeId, n]));
const ordered = [];
const walk = n => { ordered.push(n); (n.childIds ?? []).forEach(id => byId.has(id) && walk(byId.get(id))); };
walk(nodes.find(n => !n.parentId));
const visible = ordered.filter(n => !n.ignored);
const prop = (n, key) => n.properties?.find(p => p.name === key)?.value.value;
const label = n => `"${n.name?.value ?? ''}"`;
const lists = {
landmarks: visible.filter(n => landmarkRoles.includes(n.role?.value)).map(n => `${n.role.value} ${label(n)}`),
headings: visible.filter(n => n.role?.value === 'heading').map(n => `h${prop(n, 'level')} ${label(n)}`),
links: visible.filter(n => n.role?.value === 'link').map(label),
buttons: visible.filter(n => n.role?.value === 'button').map(label),
'form fields': visible.filter(n => fieldRoles.includes(n.role?.value))
.map(n => `${n.role.value} ${label(n)}${prop(n, 'required') ? ' required' : ''}${prop(n, 'checked') === 'true' ? ' checked' : ''}`),
};
console.log(`${name}: "${await page.title()}"`);
for (const [list, items] of Object.entries(lists)) {
console.log(` ${list} (${items.length}): ${items.length > 6 ? [...items.slice(0, 5), `… ${items.at(-1)}`].join(', ') : items.join(', ')}`);
}
await browser.close();
node elements.mjs catalog
node elements.mjs book
node elements.mjs cart
catalog: "Bookstore"
landmarks (5): banner "", navigation "", main "", form "", contentinfo ""
headings (1): h1 "Catalog"
links (26): "Skip to main content", "Bookstore", "Catalog", " Cart (0)", "1984", … "Accessibility statement"
buttons (22): "Search", "Filters", "Add to cart: 1984", "Add to cart: Anna Karenina", "Add to cart: Brave New World", … "Add to cart: Wuthering Heights"
form fields (6): searchbox "Search books", radio "All" checked, radio "Classics", radio "Science Fiction", radio "Fantasy", radio "Dystopian"
book: "Dune · Bookstore"
landmarks (4): banner "", navigation "", main "", contentinfo ""
headings (1): h1 "Dune"
links (7): "Skip to main content", "Bookstore", "Catalog", " Cart (0)", " Back to catalog", … "Accessibility statement"
buttons (1): "Add to cart"
form fields (1): spinbutton "Quantity"
cart: "Cart · Bookstore"
landmarks (5): banner "", navigation "", main "", form "", contentinfo ""
headings (1): h1 "Your cart"
links (7): "Skip to main content", "Bookstore", "Catalog", " Cart (1)", "1984", … "Accessibility statement"
buttons (2): "Remove 1984", "Place order"
form fields (3): spinbutton "Quantity of 1984", textbox "Full name" required, textbox "E-mail" required
Tahminin ikinci sorusunun cevabı: kataloğun heading listesinde tek satır var, "Catalog". Yirmi kitap, adlarıyla link listesinde duruyor ama bir screen reader kullanıcısının en sık kullandığı listede yok; H tuşuna basan kullanıcı sayfanın sonuna atlar. Landmark listesinde de arama kutusu yok. form "" satırı Chromium'un adsız bir <form>'u form role'üyle göstermesinden; ARIA'ya (WAI-ARIA 1.2; HTML element'lerine role, state ve property ekleyen standart) göre form ancak bir adı varsa landmark sayılır ve NVDA gibi screen reader'lar adsız formu landmark listesine koymaz. Link adlarındaki baştaki boşluk (" Cart (0)") template'teki satır sonundan geliyor; screen reader'lar boşluğu okumaz.
İki Düzeltme: search Landmark'ı ve Kitap Başlıkları
Her kitabın adını bir h2'ye aldık ve arama formunu <search> element'iyle sardık. <search>'ün implicit role'ü search; Ekim 2023'te Baseline newly available, Nisan 2026'da widely available oldu ve HTML serisinin "Semantic HTML ve Landmark'lar" yazısında anlatıldı:
<section class="toolbar">
<!-- <search> arama landmark'ıdır: NVDA'da "D" ve rotor'un Landmarks listesi onu bulur -->
<search>
<form (submit)="applySearch($event, searchBox.value)">
<input #searchBox type="search" placeholder="Search by title, author or ISBN" aria-label="Search books" />
<button type="submit">Search</button>
</form>
</search>
<li class="book-card">
<!-- Her kitap için bir heading: screen reader kullanıcılarının çoğu içeriği önce heading'lerle arar -->
<h2 class="book-title"><a [routerLink]="['/books', book.isbn]">{{ book.title }}</a></h2>
catalog: "Bookstore"
landmarks (6): banner "", navigation "", main "", search "", form "", contentinfo ""
headings (21): h1 "Catalog", h2 "1984", h2 "Anna Karenina", h2 "Brave New World", h2 "Crime and Punishment", … h2 "Wuthering Heights"
links (26): "Skip to main content", "Bookstore", "Catalog", " Cart (0)", "1984", … "Accessibility statement"
buttons (22): "Search", "Filters", "Add to cart: 1984", "Add to cart: Anna Karenina", "Add to cart: Brave New World", … "Add to cart: Wuthering Heights"
form fields (6): searchbox "Search books", radio "All" checked, radio "Classics", radio "Science Fiction", radio "Fantasy", radio "Dystopian"
Heading listesinde artık 21 satır var ve landmark listesinde search görünüyor. Kart bir h2 ile başladığı için tree'de de kitap adının bir heading olduğu görülüyor:
- listitem:
- heading "1984" [level=2]:
- link "1984":
- /url: /books/9780451524935
- text: George Orwell · 1949 Dystopian
- strong: ₺279.00
- 'button "Add to cart: 1984"': Add to cart
Heading'i görsel olarak büyütmek istemediğimiz için .book-title'a normal yazı boyutu verdik; heading'in görsel bir büyüklük değil, yapı bilgisi olduğunu unutma. Kitap sayfası ve sepet için listeler değişmedi: tek bir h1 ve kısa içerik orada yeterli. node scan.mjs dört sayfada yine sıfır ihlal veriyor; axe bu iki sorunu ihlal saymıyordu, çünkü ikisi de WCAG'de doğrudan bir başarısızlık değil, kullanılabilirlik konusu (1.3.1 ve 2.4.6'ya yakın).
Bookstore Test Senaryosu ve Beklenen Konuşma
Manuel screen reader testi en çok zaman alan test, bu yüzden her seferinde ne yapacağını ve ne duymayı beklediğini önceden yazmak işe yarıyor. Bookstore'un ana akışı için (ara, filtrele, kitabı aç, sepete ekle, siparişi ver) bir test script'i üreten küçük bir Playwright script'i yazdık. Her adım için NVDA ve VoiceOver'da basılacak tuşları ve tree'nin o adımda verdiği bilgiyi yazıyor; bildirimleri önceki yazıdaki kaydediciyle (artık ayrı bir module, recorder.mjs) topluyor:
// Manuel screen reader oturumu için test script'ini yazar: Bookstore'da ara -> filtrele -> kitap -> sepet ->
// sipariş akışı, NVDA ve VoiceOver'da basılacak tuşlar ve accessibility tree'ye göre screen reader'ın her adımda
// alması gereken bilgi. "heard" sütunları oturum sırasında elle doldurulur.
// Kullanım: node sr-script.mjs
import { chromium } from 'playwright';
import { base, focusedAx } from './lib.mjs';
import { recorder } from './recorder.mjs';
const browser = await chromium.launch();
const context = await browser.newContext();
await context.addInitScript(recorder);
const page = await context.newPage();
const cdp = await context.newCDPSession(page);
const rows = [];
// Chromium'un tree'sinden bu role'ü (ve adı) taşıyan node; browse mode'un ulaşacağı gibi
async function nodeText(role, name) {
const { nodes } = await cdp.send('Accessibility.getFullAXTree');
const n = nodes.find(x => !x.ignored && x.role?.value === role && (name === undefined || x.name?.value === name));
if (!n) return `(no ${role}${name ? ` "${name}"` : ''} in the tree)`;
const level = n.properties?.find(p => p.name === 'level')?.value.value;
return `${role}${level ? ` level ${level}` : ''} "${n.name?.value ?? ''}"`;
}
// expected() kısa bir beklemeden sonra çalışır: focus yeni sayfa render edildikten biraz sonra taşınabilir
async function step(task, nvda, voiceOver, expected = async () => '') {
await page.waitForTimeout(400);
const said = await page.evaluate(() => window.__announcements.splice(0));
rows.push([task, nvda, voiceOver, [await expected(), ...said].filter(Boolean).join(' + ')]);
}
await page.goto(base + '/');
await page.locator('.book-card').first().waitFor();
await step('open the catalog', 'page loads', 'page loads', async () => `title "${await page.title()}"`);
await step('jump to the search', 'D', 'VO+U, Landmarks', async () => await nodeText('search'));
const search = page.getByRole('searchbox', { name: 'Search books' });
await search.focus();
await step('go to the search field', 'E', 'VO+Command+J', async () => await focusedAx(page));
task | NVDA | VoiceOver | expected from the tree
open the catalog | page loads | page loads | title "Bookstore" + live polite: "20 books"
jump to the search | D | VO+U, Landmarks | (no search in the tree)
go to the search field | E | VO+Command+J | searchbox "Search books"
type "dune", Enter | type, Enter | type, Return | live polite: "1 book for "dune""
go to the categories | R | VO+Command+J | radio "All"
select Science Fiction | R R, Space | VO+Command+J x2, VO+Space | radio "Science Fiction" + live polite: "1 book in Science Fiction for "dune""
find the book | H | VO+Command+H | (no heading "Dune" in the tree)
open the book | Enter | VO+Space | heading "Dune", title "Dune · Bookstore"
add to the cart | B, Enter | Tab Tab, VO+Space | button "Add to cart" + ariaNotify normal: "Dune added to your cart. Cart: 1 item."
go to the cart | Tab, Enter | Tab, VO+Space | heading "Your cart", title "Cart · Bookstore"
go to the form | F until "Full name" | VO+Command+J until "Full name" | textbox "Full name"
fill in, Enter | type, Tab, type, Enter | type, Tab, type, Return | heading "Order #1 placed (Placed). Total: ₺489.00"
Düzeltmelerden önce iki adım boş kalıyordu: "jump to the search" ve "find the book". Düzeltmeden sonra:
jump to the search | D | VO+U, Landmarks | search ""
find the book | H | VO+Command+H | heading level 2 "Dune"
Bu tabloyu gerçek oturumda yanına iki sütun ekleyerek kullanıyorsun: "heard (NVDA + Chrome)" ve "heard (VoiceOver + Safari)". Speech Viewer'dan kopyaladığın metni adım adım bu sütunlara yazarsın. Bu yazıdaki oturum çalıştırılmadı; iki sütun da boş. Tree'nin söylediğiyle duyduğun arasında fark olan her satır bir bulgudur: ya uygulamada bir sorun vardır ya da tarayıcı ile screen reader arasında bir uyumsuzluk. İkinci durumda yapılacak şey çoğu zaman markup'ı daha sade hâle getirmek.
Tree ile Konuşma Arasındaki Farklar
Tree, screen reader'ın aldığı bilgi; konuşma ise o bilginin bir yorumu. Aradaki farkları bilmek, otomatik ölçümün nerede bittiğini gösterir.
Birincisi sıra ve ayrıntı. NVDA bir radio button'ı "radio button not checked Classics" gibi bir sırayla okuyabilir, VoiceOver başka bir sırayla; verbosity ayarları role'ün, durumun ya da description'ın okunup okunmayacağını değiştirir. İkincisi tekrar. Kategori grubunun tree'sinde etiket metni iki kez görünüyor:
- group "Category":
- text: Category
- radio "All" [checked]
- text: All
- radio "Classics"
Radio'nun adı da etiketin metni de "Classics". Browse mode'da bunun bir kez mi iki kez mi okunduğu ancak bir oturumda görülür; script'e "kategori listesinde tekrar var mı" diye bir not düşmek yeterli. Üçüncüsü zamanlama. aria-describedby ile bağlanan description bazı screen reader'larda kısa bir beklemeden sonra okunur; live region'daki polite bir mesaj, kullanıcı başka bir şey okurken sıraya girer. Tree bu zamanlamayı göstermez. Dördüncüsü tree'nin göstermediği şeyler: önceki yazılarda gördüğümüz gibi Playwright'ın aria snapshot'ı aria-current'ı yazmıyor, CDP'nin (Chrome DevTools Protocol; Chromium'u dışarıdan yöneten ve inceleyen protokol) listesi de bazı state'leri atlıyor. Hangi aracın neyi gösterdiğini bilmek, eksik bir satırı "sorun yok" diye okumamak için gerekli.
Chrome DevTools'ta aynı bilgiye Elements panelindeki Accessibility sekmesinden ulaşırsın; sekmedeki "Enable full-page accessibility tree" seçeneği Elements panelini DOM yerine tree'yi gösterecek hâle getirir. Bu panelin genel kullanımı Browser DevTools serisinin "Chrome DevTools'a Giriş ve Elements Paneli" yazısında. Firefox'un Accessibility Inspector'ı da kendi tree'sini gösterir; bu yazıda Firefox ve WebKit kullanılmadı.
Üretimde Dikkat
Manuel screen reader testini her commit'te değil, akış değiştiğinde yap: yeni bir sayfa, yeni bir composite widget, form akışında bir değişiklik. Geri kalan zamanda tree'den üretilen script'ler (bu yazıdaki gibi) ve otomatik taramalar regresyonu yakalar.
Test kombinasyonlarını yaz ve sabitle. "NVDA 2026.3 + Chrome, VoiceOver + Safari (macOS 27)" gibi bir satır, bulguları tekrar üretilebilir kılar; screen reader ve tarayıcı sürümleri değiştikçe davranış değişebilir.
Gerçek kullanıcılarla test et. Bir geliştiricinin screen reader kullanması, her gün kullanan birinin hızına ve alışkanlıklarına yaklaşmaz; uzman kullanıcılar tuşlarla, ayarlarla ve tahminle çok daha hızlı ilerler. Kullanıcı testi, accessibility statement'ta "nasıl kontrol ettik" bölümünün de en güçlü satırı olur.
Accessibility statement'ı güncel tut. Bookstore'un beyanında "Screen reader testing is planned" yazıyor; ilk gerçek oturumdan sonra hangi kombinasyonla ve ne zaman test edildiği oraya yazılmalı.
Sık Düşülen Tuzaklar
Birincisi, screen reader kullanıcısının sayfayı baştan sona okuduğunu varsaymak. Çoğu kullanıcı heading, landmark ve link listelerinden gider; bu listeler anlamsızsa sayfa kullanılmaz hâle gelir.
İkincisi, ekranda büyük görünen her metni heading sanmak ya da heading'i yalnız stil için kullanmak. Heading yapıyı anlatır; görsel boyut CSS'in işi.
Üçüncüsü, composite widget'ta yalnız keydown ile ok tuşu dinlemek ve role vermemek. Browse mode'daki screen reader tuşları yakalar ve component hiç tuş almaz.
Dördüncüsü, tek bir kombinasyonla test edip sonucu genellemek. VoiceOver + Safari'de çalışan bir live region, NVDA + Chrome'da farklı davranabilir.
Beşincisi, screen reader'ı yalnız "okuyor mu" diye test etmek. Asıl soru görevin tamamlanıp tamamlanamadığı: kullanıcı kitabı bulup siparişi verebiliyor mu?
Küçük Bir Deneme
★☆☆ elements.mjs'i statement sayfası için çalıştır. Heading listesi beyanın yapısını tek başına anlatıyor mu? Bir başlığı kaldırsan hangi bilgi kaybolur?
★★☆ sr-script.mjs'e sepetteki bir kitabı silme adımlarını ekle (Remove, dialog, Keep it / Remove). Focus'un dialog'dan sonra nereye gittiğini script'in çıktısında gör ve serinin keyboard yazısındaki ölçümle karşılaştır.
★★★ Windows'ta NVDA'yı kur, Chrome ile Bookstore'u aç ve sr-script.mjs'in tablosunu Speech Viewer'dan kopyaladığın metinle doldur. Tree'nin söylediğiyle duyduğun arasında kaç satır farklı? Aynı akışı mümkünse VoiceOver + Safari ile tekrarla.
Elle yazdığımız filtre paneli, disclosure ve radio grubu birer component olarak çalışıyor; ama bir combobox ya da tabs pattern'ini bu dikkatle sıfırdan yazmak hem uzun hem hataya açık. Bir sonraki yazıda Angular Aria, CDK (Component Dev Kit; Angular ekibinin davranış library'si) ve Angular Material'ın hazır accessible component'lerine ve kendi combobox'ını neden yazmaman gerektiğine bakıyoruz.