---
title: 'Ekran Okuyucu Deneyimi'
source: 'https://academia.sh/tr/kurslar/frontend-kalitesi/ekran-okuyucu-deneyimi'
course: 'Frontend Kalitesi'
language: tr
updated: '2026-08-17T18:09:21+00:00'
license: 'CC BY-SA 4.0'
---

# Ekran Okuyucu Deneyimi

Belgenin gezinme düzlemleri ve düzlem kusurlarının denetimi, duyurunun neyi taşıdığı, canlı bölgelerin kibar ve atılgan önceliği ile duyuru kuyruğunun davranışı.

Önceki ders odağın nereye gittiğini hesapladı. Odak oraya vardığında kullanıcının ne
öğrendiği ayrı bir sorudur ve yanıtı iki kanaldan gelir: ögenin kendisinden gelen bilgi ve
ögeden bağımsız olarak değişen içeriğin bildirimi.

Bu dersin konusu iki kanaldır. Ekran okuyucuların iç işleyişi ve söyledikleri sözcükler
araca göre değişir; burada anlatılan, **ağaçta tanımlı olan bilginin ne olduğu** ve
duyuruların hangi öncelik kurallarına göre sıraya girdiğidir. Bu kurallar araca değil
tanıma bağlıdır.

## Sayfa Doğrusal Okunmaz

Ekran okuyucu kullanıcısı bir sayfayı baştan sona dinlemez; bu, uzun bir belgede
kullanılamaz bir yol olurdu. Kullanılan yol, belgenin **gezinme düzlemleri** arasında
atlamaktır: yer imlerinden bölüme, başlıklardan konuya, bağlantı listesinden hedefe,
denetim listesinden forma.

Her düzlem belgeden türetilir ve türetme kusurları düzlemi kullanılmaz kılar. Aşağıdaki
betik ölçüm listesi sayfasından üç düzlemi çıkarıyor ve her birini kendi ölçütüyle
denetliyor.

```js
// duzlem.mjs — bir belgeden uc gezinme duzleminin turetilmesi ve kusur denetimi

const YER_IMI = { header: "banner", nav: "navigation", main: "main", aside: "complementary",
  footer: "contentinfo", section: "region", form: "form" };

// ust duzey: belgenin dogrudan cocugu mu
const BELGE = [
  { oge: "header", ustDuzey: true },
  { oge: "nav", ustDuzey: false, etiket: "Ağ genelinde gezinme" },
  { oge: "main", ustDuzey: true },
  { oge: "h1", metin: "Kuzey Yamaç Ölçüm İstasyonu" },
  { oge: "form", ustDuzey: false, etiket: "Süzgeç" },
  { oge: "a", metin: "Ayrıntı", hedef: "/olcum/T-01" },
  { oge: "h3", metin: "Günlük Ölçümler" },
  { oge: "a", metin: "Ayrıntı", hedef: "/olcum/T-02" },
  { oge: "a", metin: "buraya tıklayın", hedef: "/yontem.html" },
  { oge: "h2", metin: "Uyarılar" },
  { oge: "nav", ustDuzey: false },
  { oge: "a", metin: "Ölçüm yöntemi belgesi", hedef: "/yontem.html" },
  { oge: "footer", ustDuzey: true },
];

const BELIRSIZ = new Set(["buraya tıklayın", "tıklayın", "devamı", "daha fazla", "bağlantı"]);

function yerImleri(belge) {
  const liste = [];
  for (const d of belge) {
    let rol = YER_IMI[d.oge];
    if (!rol) continue;
    if ((d.oge === "header" || d.oge === "footer") && !d.ustDuzey) continue;
    if ((d.oge === "section" || d.oge === "form") && !d.etiket) continue;
    liste.push({ rol, etiket: d.etiket ?? null });
  }
  return liste;
}

const basliklar = (belge) =>
  belge.filter((d) => /^h[1-6]$/.test(d.oge)).map((d) => ({ duzey: Number(d.oge[1]), metin: d.metin }));

const baglantilar = (belge) => belge.filter((d) => d.oge === "a");

console.log("yer imi duzlemi:");
for (const y of yerImleri(BELGE)) console.log(`  ${y.rol}${y.etiket ? ': "' + y.etiket + '"' : ""}`);

console.log("\nbaslik duzlemi:");
for (const b of basliklar(BELGE)) console.log(`  ${"  ".repeat(b.duzey - 1)}h${b.duzey} ${b.metin}`);

console.log("\nbaglanti duzlemi:");
for (const b of baglantilar(BELGE)) console.log(`  "${b.metin}" -> ${b.hedef}`);

console.log("\nkusur denetimi:");
const bulgular = [];

// 1. Ayni turden adsiz yer imi birden fazla
const sayim = {};
for (const y of yerImleri(BELGE)) sayim[y.rol] = (sayim[y.rol] ?? 0) + 1;
for (const y of yerImleri(BELGE)) {
  if (sayim[y.rol] > 1 && !y.etiket) bulgular.push(`adsiz ${y.rol} yer imi, ayni rolden ${sayim[y.rol]} tane var`);
}

// 2. Baslik duzeyi atlamasi
let onceki = 0;
for (const b of basliklar(BELGE)) {
  if (onceki && b.duzey > onceki + 1) bulgular.push(`baslik duzeyi atlandi: h${onceki} -> h${b.duzey} ("${b.metin}")`);
  onceki = b.duzey;
}

// 3. Ayni metin farkli hedef / farkli metin ayni hedef / belirsiz metin
const metinler = {};
const hedefler = {};
for (const b of baglantilar(BELGE)) {
  (metinler[b.metin] ??= new Set()).add(b.hedef);
  (hedefler[b.hedef] ??= new Set()).add(b.metin);
  if (BELIRSIZ.has(b.metin.toLocaleLowerCase("tr"))) bulgular.push(`baglamdan kopuk baglanti metni: "${b.metin}"`);
}
for (const [m, h] of Object.entries(metinler)) {
  if (h.size > 1) bulgular.push(`"${m}" metni ${h.size} farkli hedefe gidiyor`);
}
for (const [h, m] of Object.entries(hedefler)) {
  if (m.size > 1) bulgular.push(`${h} hedefine ${m.size} farkli metinle baglaniliyor`);
}

for (const b of bulgular) console.log("  - " + b);
console.log(`  toplam ${bulgular.length} bulgu`);
```

```
yer imi duzlemi:
  banner
  navigation: "Ağ genelinde gezinme"
  main
  form: "Süzgeç"
  navigation
  contentinfo

baslik duzlemi:
  h1 Kuzey Yamaç Ölçüm İstasyonu
      h3 Günlük Ölçümler
    h2 Uyarılar

baglanti duzlemi:
  "Ayrıntı" -> /olcum/T-01
  "Ayrıntı" -> /olcum/T-02
  "buraya tıklayın" -> /yontem.html
  "Ölçüm yöntemi belgesi" -> /yontem.html

kusur denetimi:
  - adsiz navigation yer imi, ayni rolden 2 tane var
  - baslik duzeyi atlandi: h1 -> h3 ("Günlük Ölçümler")
  - baglamdan kopuk baglanti metni: "buraya tıklayın"
  - "Ayrıntı" metni 2 farkli hedefe gidiyor
  - /yontem.html hedefine 2 farkli metinle baglaniliyor
  toplam 5 bulgu
```

Beş bulgunun her biri ayrı bir düzlemi bozuyor.

**Adsız ikinci gezinme bloğu** yer imi listesinde ayırt edilemez: kullanıcı iki
"navigation" girdisi görür ve hangisinin sayfa içi gezinme olduğunu ancak ikisini de
açarak öğrenir. Çözüm ada bakar, yapıya değil.

**Başlık düzeyi atlaması** ana hattı bozar. Çıktının girintileri sorunu gösteriyor: h1'den
sonra gelen h3, ardından gelen h2'nin altına düşmesi gereken bir konuyu onun üstüne
koyuyor. Başlık düzlemi belgenin içindekiler dizinidir; atlanan düzey, dizinde var olmayan
bir kırılma üretir.

**Bağlantı düzleminde üç kusur** birlikte görünüyor. Bağlamdan kopuk metin, listede tek
başına okunduğunda hiçbir şey söylemez. Aynı metnin iki farklı hedefe gitmesi, listede
ayırt edilemeyen iki giriş üretir — ölçüm tablosundaki her satırın "Ayrıntı" bağlantısı
tam bu durumdur ve çözümü bağlantı metnine ölçüm kodunu eklemektir. Aynı hedefe iki farklı
metinle bağlanmak ise ters yönde bir kusurdur: kullanıcı iki ayrı sayfa olduğunu sanır.

## Duyuru Neyi Taşır

Odak bir ögeye vardığında kullanıcıya iletilen bilgi, o ögenin ağaçtaki dört alanından
gelir: **rolü**, **erişilebilir adı**, **değeri** ve **durumları**; buna bir de
`aria-describedby` ile bağlanan **ek açıklama** eklenir. İletim sırası ve sözcükler araca
göre değişir, alanların içeriği değişmez.

Buradan iki pratik kural çıkar. Birincisi, ada rol adı yazılmaz: adı "Süz düğmesi" olan bir
düğme, rolüyle birlikte iki kez düğme diye anılır. İkincisi, ek açıklama ada eklenmez,
adın **ardından** gelir; uzun yardım metni ada değil açıklamaya yazılır, yoksa denetimin
adı bir paragrafa dönüşür.

## Canlı Bölgeler ve Öncelik

Sayfa yenilenmeden değişen içerik ayrı bir sorundur: kullanıcı orada değilse değişimi
öğrenemez. **Canlı bölge (live region)**, içeriği değiştiğinde bildirilen bölümdür ve Web
Temelleri ve HTML kursunda hata özeti için kurulmuştu.

Bölgenin iki önceliği vardır. **Kibar (polite)** öncelik, bildirimi sıraya koyar ve
kullanıcının o anki okumasını bölmez; durum iletileri buraya girer. **Atılgan (assertive)**
öncelik, bekleyenleri geçersiz kılarak öne geçer; yalnızca kullanıcının işini durduran
bildirimler —veri kaybı, gönderim hatası— buraya girer. `role="status"` bildirimi kibar,
`role="alert"` bildirimi atılgan bir bölge kurar.

Aşağıdaki betik bu önceliği bir kuyruk olarak modelliyor. Model, iletimin ne kadar
süreceğini değil, tanımdaki önceliğin sıraya ne yaptığını gösterir.

```js
// duyuru.mjs — canli bolge duyurularinin kibar/atilgan onceligiyle kuyruklanma modeli

const BOLGE = {
  "suzgec-durumu": { rol: "status", oncelik: "kibar" },
  "gonderim-durumu": { rol: "status", oncelik: "kibar" },
  "hata-ozeti": { rol: "alert", oncelik: "atilgan" },
};

const SURE = 2; // bir duyurunun kapladigi an sayisi

function calistir(baslik, olaylar, sonAn) {
  console.log(baslik);
  console.log("  an  " + "gelen".padEnd(38) + "duyurulan".padEnd(24) + "kuyruk");
  let kuyruk = [];          // bekleyen kibar duyurular
  let etkin = null;         // { metin, kalan }
  let degistirilen = 0, atilan = 0;

  for (let an = 0; an <= sonAn; an++) {
    const gelen = olaylar.filter((o) => o.an === an);
    let gelenMetin = [];
    for (const o of gelen) {
      const b = BOLGE[o.bolge];
      gelenMetin.push(`${o.bolge}: ${o.metin}`);
      if (b.oncelik === "atilgan") {
        atilan += kuyruk.length + (etkin ? 1 : 0);
        kuyruk = [];
        etkin = { metin: o.metin, kalan: SURE };      // suren duyuruyu boler
      } else {
        const i = kuyruk.findIndex((k) => k.bolge === o.bolge);
        if (i >= 0) { kuyruk[i] = { ...o }; degistirilen++; }   // bolgenin icerigi yenilendi
        else kuyruk.push({ ...o });
      }
    }
    if (!etkin && kuyruk.length > 0) etkin = { metin: kuyruk.shift().metin, kalan: SURE };

    const duyurulan = etkin && etkin.kalan === SURE ? etkin.metin : etkin ? "(suruyor)" : "—";
    console.log(
      `  ${String(an).padStart(2)}  ${(gelenMetin.join(" | ") || "—").padEnd(38)}` +
        `${duyurulan.padEnd(24)}${kuyruk.length === 0 ? "—" : kuyruk.map((k) => k.metin).join(" | ")}`,
    );
    if (etkin && --etkin.kalan === 0) etkin = null;
  }
  console.log(`  yenilenen bekleyen duyuru: ${degistirilen}, atilgan duyurunun attigi: ${atilan}`);
}

const HER_TUSTA = [
  { an: 0, bolge: "suzgec-durumu", metin: "12 ölçüm listeleniyor" },
  { an: 1, bolge: "suzgec-durumu", metin: "5 ölçüm listeleniyor" },
  { an: 2, bolge: "suzgec-durumu", metin: "3 ölçüm listeleniyor" },
  { an: 3, bolge: "gonderim-durumu", metin: "Kayıt gönderiliyor" },
  { an: 4, bolge: "hata-ozeti", metin: "Kayıt gönderilemedi" },
  { an: 7, bolge: "gonderim-durumu", metin: "Kayıt gönderildi" },
];

const GECIKMELI = [
  { an: 2, bolge: "suzgec-durumu", metin: "3 ölçüm listeleniyor" },
  { an: 3, bolge: "gonderim-durumu", metin: "Kayıt gönderiliyor" },
  { an: 6, bolge: "hata-ozeti", metin: "Kayıt gönderilemedi" },
  { an: 9, bolge: "gonderim-durumu", metin: "Kayıt gönderildi" },
];

calistir("her tus vurusunda duyuru:", HER_TUSTA, 9);
console.log("");
calistir("gecikmeli tetiklemeyle tek duyuru:", GECIKMELI, 11);
```

```
her tus vurusunda duyuru:
  an  gelen                                 duyurulan               kuyruk
   0  suzgec-durumu: 12 ölçüm listeleniyor  12 ölçüm listeleniyor   —
   1  suzgec-durumu: 5 ölçüm listeleniyor   (suruyor)               5 ölçüm listeleniyor
   2  suzgec-durumu: 3 ölçüm listeleniyor   3 ölçüm listeleniyor    —
   3  gonderim-durumu: Kayıt gönderiliyor   (suruyor)               Kayıt gönderiliyor
   4  hata-ozeti: Kayıt gönderilemedi       Kayıt gönderilemedi     —
   5  —                                     (suruyor)               —
   6  —                                     —                       —
   7  gonderim-durumu: Kayıt gönderildi     Kayıt gönderildi        —
   8  —                                     (suruyor)               —
   9  —                                     —                       —
  yenilenen bekleyen duyuru: 1, atilgan duyurunun attigi: 1

gecikmeli tetiklemeyle tek duyuru:
  an  gelen                                 duyurulan               kuyruk
   0  —                                     —                       —
   1  —                                     —                       —
   2  suzgec-durumu: 3 ölçüm listeleniyor   3 ölçüm listeleniyor    —
   3  gonderim-durumu: Kayıt gönderiliyor   (suruyor)               Kayıt gönderiliyor
   4  —                                     Kayıt gönderiliyor      —
   5  —                                     (suruyor)               —
   6  hata-ozeti: Kayıt gönderilemedi       Kayıt gönderilemedi     —
   7  —                                     (suruyor)               —
   8  —                                     —                       —
   9  gonderim-durumu: Kayıt gönderildi     Kayıt gönderildi        —
  10  —                                     (suruyor)               —
  11  —                                     —                       —
  yenilenen bekleyen duyuru: 0, atilgan duyurunun attigi: 0
```

İlk çalıştırma iki kayıp üretiyor. Süzgeç alanına yazılan her karakter bir duyuru
tetiklediği için bekleyen sayı duyurusu bir kez yenileniyor: kullanıcı "12" duyurusunu
dinlerken sayı önce beşe, sonra üçe düşüyor ve duyurulan ilk sayı zaten geçersiz. İkinci
kayıp daha ağır: atılgan hata bildirimi geldiğinde bekleyen "Kayıt gönderiliyor" duyurusu
atılıyor. Kullanıcı, gönderimin başladığını hiç duymadan başarısız olduğunu duyuyor.

İkinci çalıştırma iki düzeltmeyi birlikte uyguluyor. Süzgeç duyurusu her tuşta değil,
yazma durduktan sonra bir kez tetikleniyor; bu, Tarayıcı ve Web Platformu kursundaki
gecikmeli tetiklemenin erişilebilirlik karşılığıdır. Hata bildirimi de kuyruk boşaldıktan
sonraya düşüyor. Kayıp sayısı sıfıra iniyor.

Buradan üç kural çıkar. Kibar bölgeye yazılan metin **son durumu** bildirmelidir, ara
durumları değil. Atılgan öncelik bekleyenleri attığı için yalnızca kullanıcının işini
durduran bildirimlerde kullanılır. Bir bölge sık güncelleniyorsa duyuru güncellemeden değil,
**durulmadan** sonra yazılır.

## Bölgenin Kurulumu

Canlı bölgenin belgede **önceden** bulunması gerekir; sonradan eklenen bir bölgenin
içeriği bildirilmeyebilir. Boş bir kap sayfa kurulurken konur ve içeriği sonradan
doldurulur.

Bölge içeriği kısa ve durum bildiren bir cümle olmalıdır. Tabloyu canlı bölgeye almak —
yani tablonun kendisini `aria-live` bildirimiyle işaretlemek— her satır değişiminde bütün
içeriğin bildirilmesine yol açar. Doğru kurulum, tablonun dışında bir durum bölgesi
tutmak ve oraya "3 ölçüm listeleniyor" yazmaktır. Tablonun kendisi sessiz kalır; kullanıcı
sayıyı duyduktan sonra tabloya kendi gider.

4.1.3 ölçütü bunu bir gereklilik hâline getirir: odak değiştirmeden sunulan durum iletileri
program yoluyla belirlenebilir olmalı ve odak taşımadan bildirilmelidir. Ölçütün iki yönü
vardır — ileti bildirilmelidir, ama bildirim için **odak çalınmamalıdır**. Süzgeç
sonucundan sonra odağı tabloya taşıyan kod, ölçütü ters yönden kaldırır.

## Özet

- Ekran okuyucu kullanıcısı belgeyi doğrusal okumaz; yer imi, başlık ve bağlantı
  düzlemleri arasında atlar ve her düzlem belgeden türetilir.
- Düzlem kusurları sayılabilir: adsız yinelenen yer imi, atlanan başlık düzeyi, bağlamdan
  kopuk bağlantı metni, aynı metinle farklı hedef ve farklı metinle aynı hedef.
- Odak bir ögeye vardığında iletilen bilgi rol, ad, değer, durum ve ek açıklamadan gelir;
  ada rol adı yazılmaz, uzun metin ada değil açıklamaya yazılır.
- Kibar öncelik duyuruyu sıraya koyar, atılgan öncelik bekleyenleri atarak öne geçer;
  atılgan bildirim yalnızca işi durduran durumlar içindir.
- Sık güncellenen bir bölgede duyuru her değişimde değil durulmadan sonra yazılır ve son
  durumu bildirir.
- Canlı bölge belgede önceden bulunmalı, içeriği kısa olmalı ve bildirim odağı
  taşımamalıdır.

## Sonraki Adım

Bu ders bilginin görsel olmayan kanallardan nasıl aktarıldığını kurdu. Aynı bilginin görsel
kanaldaki karşılığı ölçülebilir bir eşiğe bağlıdır: ekranda duran metnin ve bileşen
sınırlarının çevresinden ayırt edilebilmesi. Sonraki ders karşıtlığı hesaplanabilir bir
nicelik olarak ele alır — bağıl parlaklıktan kontrast oranının türetilmesini, metin ve
metin dışı ögelerin farklı eşiklerini ve süzgeç panelinin bütün durumlarının bu eşiklere
karşı denetlenmesini.
