---
title: 'Erişilebilir Hata Sunumu'
source: 'https://academia.sh/tr/kurslar/frontend-mimarisi/erisilebilir-hata-sunumu'
course: 'Uygulama Mimarisi: Yönlendirme, Durum ve Veri'
language: tr
updated: '2026-08-17T18:09:20+00:00'
license: 'CC BY-SA 4.0'
---

# Erişilebilir Hata Sunumu

Hata iletisinin alanla programlı olarak ilişkilendirilmesi, geçersizlik bildiriminin verilmesi, belge sırasına göre kurulan hata özeti, odağın taşınması ve iletinin hangi anda yazılacağı.

Önceki ders her alan için bir hata kodu ve parametresi üretti. Sıradaki soru bunların
ekranda nasıl görüneceği değil: bir hata iletisi, yalnız görüldüğünde iş görüyorsa yarım
kalmıştır. Ekran okuyucuyla gezen bir gözlemci ölçüm giriş formunu gönderdiğinde, kırmızı
çerçeveyi görmez; alanın altına yazılan metnin o alanla birlikte okunması kendiliğinden
olmaz.

Bu ders hatanın **algılanabilir** hâle getirilmesini ele alır: ileti hangi ögeye bağlanır,
alanın geçersiz olduğu nasıl bildirilir, gönderim reddedildiğinde odak nereye gider ve
ileti hangi anda yazılır.

## Görmeyen Kullanıcı İçin Hata Nedir

Görsel sunum üç ipucu kullanır: renk, konum ve simge. Üçü de tek başına yetersizdir.

**Renk tek gösterge olamaz.** WCAG 1.4.1 ölçütü bunu doğrudan söyler: bilgi yalnız renkle
aktarılamaz. Kırmızı çerçeve renk ayrımı olmayan bir kullanıcı için sıradan bir çerçevedir.
Rengin yanında metin gerekir.

**Konum programlı bir bağ değildir.** İletinin alanın hemen altında durması gözle kurulan
bir ilişkidir. Erişilebilirlik ağacında iki öge arasında bir bağ yoksa, alana odaklanan
kullanıcı iletiyi hiç duymaz. Bağ açıkça kurulmalıdır.

**Hata bildirimi ile hatanın tanımı ayrı ölçütlerdir.** WCAG 3.3.1 hatanın **saptandığının**
metinle bildirilmesini, 3.3.3 ise mümkünse **düzeltme önerisinin** sunulmasını ister.
"Geçersiz değer" birincisini karşılar, ikincisini karşılamaz; "−60 ile 60 arasında bir
değer girin" ikisini birden karşılar. Bu yüzden önceki dersteki hata parametreleri
iletinin içine yazılır.

## İletiyi Alana Bağlamak

Bağ, alanın **ek açıklama** listesiyle kurulur. Web'in Temelleri ve HTML kursunda
tanımlanan bu düzenek, alana odaklanıldığında etiketten sonra okunacak metinleri belirler.
Alanın hem bir ipucu hem bir hatası varsa ikisi de listeye girer ve listedeki **sıra
okunma sırasıdır**.

```js
// hata-isaretlemesi.mjs — alan, ipucu, hata ve ozetin iliskilendirilmesi
const ILETILER = {
  zorunlu:      () => "Bu alan zorunludur.",
  "sayı_değil": () => "Sayı girin; ondalık ayırıcı olarak virgül kullanabilirsiniz.",
  "aralık_dışı": (p) => `${p.enAz} ile ${p.enCok} arasında bir değer girin.`,
  gelecekte:    () => "Ölçüm tarihi gelecekte olamaz.",
};

// Alanlar belgedeki siralarina gore tanimlanir; sira odak kararini belirler.
const ALANLAR = [
  { ad: "istasyon", etiket: "İstasyon", ipucu: null },
  { ad: "tarih",    etiket: "Ölçüm tarihi", ipucu: "Gün-ay-yıl biçiminde." },
  { ad: "deger",    etiket: "Ölçüm değeri", ipucu: "Santigrat derece." },
  { ad: "not",      etiket: "Not", ipucu: null },
];

function alanIsaretlemesi(alan, hata) {
  const kimlik = `alan-${alan.ad}`;
  const ipucuKimligi = alan.ipucu ? `${kimlik}-ipucu` : null;
  const hataKimligi = hata ? `${kimlik}-hata` : null;
  // Sira onemlidir: once ipucu, sonra hata okunur.
  const acikla = [ipucuKimligi, hataKimligi].filter(Boolean).join(" ");

  const satirlar = [`<label for="${kimlik}">${alan.etiket}</label>`];
  satirlar.push(
    `<input id="${kimlik}" name="${alan.ad}"` +
    (acikla ? ` aria-describedby="${acikla}"` : "") +
    (hata ? ` aria-invalid="true"` : "") + ">");
  if (alan.ipucu) satirlar.push(`<p id="${ipucuKimligi}">${alan.ipucu}</p>`);
  if (hata) satirlar.push(`<p id="${hataKimligi}">${ILETILER[hata.kod](hata.param)}</p>`);
  return satirlar.join("\n");
}

function ozet(hatalar) {
  // Ozet, hatalarin saptanma sirasina gore degil belgedeki alan sirasina gore dizilir.
  const sirali = ALANLAR
    .map((a) => [a, hatalar.find((h) => h.yol === a.ad)])
    .filter(([, h]) => h);
  const maddeler = sirali.map(([a, h]) =>
    `  <li><a href="#alan-${a.ad}">${a.etiket}: ${ILETILER[h.kod](h.param)}</a></li>`);
  return [
    `<div role="alert" tabindex="-1" id="hata-ozeti">`,
    `  <h2>${sirali.length} alan düzeltilmeli</h2>`,
    "  <ul>", ...maddeler, "  </ul>", "</div>",
  ].join("\n");
}

// Semadan gelen hata listesi (saptanma sirasi belge sirasindan farkli).
const HATALAR = [
  { yol: "deger", kod: "aralık_dışı", param: { enAz: -60, enCok: 60 } },
  { yol: "tarih", kod: "gelecekte" },
];

console.log(ozet(HATALAR));
console.log("");
for (const alan of ALANLAR) {
  console.log(alanIsaretlemesi(alan, HATALAR.find((h) => h.yol === alan.ad)));
}

// --- Odak hedefi -----------------------------------------------------------
const ilkGecersiz = ALANLAR.find((a) => HATALAR.some((h) => h.yol === a.ad));
console.log("\nhata sayısı :", HATALAR.length);
console.log("odak hedefi :", HATALAR.length > 1 ? "#hata-ozeti" : `#alan-${ilkGecersiz.ad}`);
console.log("tek hata olsaydı:", `#alan-${ilkGecersiz.ad}`);
```

```
<div role="alert" tabindex="-1" id="hata-ozeti">
  <h2>2 alan düzeltilmeli</h2>
  <ul>
  <li><a href="#alan-tarih">Ölçüm tarihi: Ölçüm tarihi gelecekte olamaz.</a></li>
  <li><a href="#alan-deger">Ölçüm değeri: -60 ile 60 arasında bir değer girin.</a></li>
  </ul>
</div>

<label for="alan-istasyon">İstasyon</label>
<input id="alan-istasyon" name="istasyon">
<label for="alan-tarih">Ölçüm tarihi</label>
<input id="alan-tarih" name="tarih" aria-describedby="alan-tarih-ipucu alan-tarih-hata" aria-invalid="true">
<p id="alan-tarih-ipucu">Gün-ay-yıl biçiminde.</p>
<p id="alan-tarih-hata">Ölçüm tarihi gelecekte olamaz.</p>
<label for="alan-deger">Ölçüm değeri</label>
<input id="alan-deger" name="deger" aria-describedby="alan-deger-ipucu alan-deger-hata" aria-invalid="true">
<p id="alan-deger-ipucu">Santigrat derece.</p>
<p id="alan-deger-hata">-60 ile 60 arasında bir değer girin.</p>
<label for="alan-not">Not</label>
<input id="alan-not" name="not">

hata sayısı : 2
odak hedefi : #hata-ozeti
tek hata olsaydı: #alan-tarih
```

Üretilen işaretlemede dört karar görünür.

**Ek açıklama listesi hem ipucunu hem hatayı taşır.** Hata çıktığında ipucunun listeden
düşürülmesi sık yapılan bir yanlıştır; kullanıcı hatayı duyar ama beklenen biçimi
öğrenemez. İkisi birlikte kalır ve ipucu önce okunur.

**Geçersizlik ayrıca bildirilir.** İletinin okunması, alanın geçersiz olduğunun
bildirilmesiyle aynı şey değildir. `aria-invalid="true"` alanın durumunu erişilebilirlik
ağacına yazar; ileti metniyle birlikte kullanılır, onun yerine geçmez. Aynı işi yapan bir
başka öznitelik daha vardır — hata iletisini doğrudan gösteren `aria-errormessage` — ancak
o yalnız geçersizlik bildirimi verildiğinde anlam taşır; ek açıklama bağı ise her durumda
işler. Sağlam yol, ek açıklama bağını temel almaktır.

**Hatası olmayan alanda hiçbir öznitelik bulunmaz.** İlk ve son alanda `aria-invalid`
yazılmamış; alan geçerliyken `aria-invalid="false"` yazmak da doğrudur ama gereksizdir.
Önemli olan, hata düzeldiğinde özniteliğin **kaldırılmasıdır**; kalırsa kullanıcı düzelmiş
bir alanı geçersiz duyar.

**Özet belge sırasına göre dizilir.** Şemadan gelen hata listesi `deger` ile başlıyordu,
özette `tarih` önce geldi. Kullanıcı özetteki maddelere yukarıdan aşağıya baktığında
formda da yukarıdan aşağıya ilerlemelidir; saptanma sırası bir uygulama ayrıntısıdır ve
ekrana sızmamalıdır.

## Özet Kutusu ve Odağın Taşınması

Gönderim reddedildiğinde kullanıcı iki şeyi bilmelidir: gönderimin gerçekleşmediğini ve
neyin düzeltileceğini. Sayfanın hiçbir yeri değişmemiş gibi görünüyorsa — özellikle uzun
bir formda, hatalar ekranın dışında kaldıysa — kullanıcı düğmenin çalışmadığını sanır.

**Hata özeti** formun başında durur, hata sayısını söyler ve her maddesi ilgili alana
bağlantı verir. Bağlantı olması önemlidir: klavyeyle gezen kullanıcı maddeyi seçip doğrudan
alana gider. Madde metni, alanın etiketini ve iletiyi birlikte taşır; yalnız ileti
yazıldığında hangi alandan söz edildiği anlaşılmaz.

**Odak** özet kutusuna taşınır. Kutunun odaklanabilir olması için sekme sırasına
girmeyen bir odak hedefi olarak işaretlenmesi gerekir — çıktıdaki `tabindex="-1"` bunu
yapar; öge programla odaklanabilir ama sekme turuna katılmaz.

Hata sayısına göre iki davranış ayrılır. Tek hata varsa odağı doğrudan o alana taşımak
kullanıcıyı bir adım öne alır. Birden çok hata varsa özet kutusuna taşımak, kullanıcıya
önce durumun tamamını verir. Çıktının son üç satırı bu kararı hesaplıyor.

Odak taşımanın bir sınırı vardır: kullanıcının odağını beklemediği bir yere almak da bir
kesintidir. Kural, odağın **yalnız kullanıcının kendi eylemine yanıt olarak** taşınmasıdır.
Gönderim düğmesine basmak böyle bir eylemdir; arka planda tamamlanan bir istek değildir.

## Bildirimin Anı

İşaretleme doğru olsa bile iletinin **ne zaman** göründüğü ayrı bir karardır. Kullanıcı
ölçüm değerini yazarken her tuşta "geçersiz" demek, henüz bitmemiş bir girdiyi hatalı ilan
eder ve yazmayı zorlaştırır.

```js
// gosterim-ani.mjs — hata iletisinin ne zaman yazilacagi
function gosterilirMi({ hataVar, dokunuldu, gonderimDenendi, hataGosterilmisti }) {
  if (!hataVar) return false;                 // hata yoksa gosterilecek sey yok
  if (gonderimDenendi) return true;           // gonderimden sonra hepsi gorunur
  if (dokunuldu) return true;                 // alan terk edildi
  return hataGosterilmisti;                   // bir kez gosterildiyse izlemeye devam
}

// Hata yokken cevap her zaman "hayir"; tablo yalniz hatali alani sayiyor.
const DURUMLAR = [];
for (const dokunuldu of [false, true])
  for (const gonderimDenendi of [false, true])
    for (const hataGosterilmisti of [false, true])
      DURUMLAR.push({ hataVar: true, dokunuldu, gonderimDenendi, hataGosterilmisti });

const im = (b) => (b ? "evet" : "hayır");
console.log("dokunuldu  gönderim  önceden  →  gösterilir");
for (const d of DURUMLAR) {
  console.log(
    im(d.dokunuldu).padEnd(11) + im(d.gonderimDenendi).padEnd(10) +
    im(d.hataGosterilmisti).padEnd(9) + "   " + im(gosterilirMi(d)));
}

// Kullanicinin gercek yolculugu: yazarken hata yok, terk edince var, duzeltince kayboluyor.
console.log("\nadım adım bir alanın yaşamı");
const YOL = [
  ["boş alana '-' yazıldı",   { hataVar: true,  dokunuldu: false, gonderimDenendi: false }],
  ["'-4' yazıldı",            { hataVar: false, dokunuldu: false, gonderimDenendi: false }],
  ["alan silindi",            { hataVar: true,  dokunuldu: false, gonderimDenendi: false }],
  ["alan terk edildi",        { hataVar: true,  dokunuldu: true,  gonderimDenendi: false }],
  ["geri dönülüp '9' yazıldı",{ hataVar: false, dokunuldu: true,  gonderimDenendi: false }],
  ["'900' yapıldı",           { hataVar: true,  dokunuldu: true,  gonderimDenendi: false }],
];
let onceden = false;
for (const [ad, kismi] of YOL) {
  const g = gosterilirMi({ ...kismi, hataGosterilmisti: onceden });
  onceden = g;
  console.log(`  ${ad.padEnd(27)} hata=${im(kismi.hataVar).padEnd(6)} → ${im(g)}`);
}
```

```
dokunuldu  gönderim  önceden  →  gösterilir
hayır      hayır     hayır       hayır
hayır      hayır     evet        evet
hayır      evet      hayır       evet
hayır      evet      evet        evet
evet       hayır     hayır       evet
evet       hayır     evet        evet
evet       evet      hayır       evet
evet       evet      evet        evet

adım adım bir alanın yaşamı
  boş alana '-' yazıldı       hata=evet   → hayır
  '-4' yazıldı                hata=hayır  → hayır
  alan silindi                hata=evet   → hayır
  alan terk edildi            hata=evet   → evet
  geri dönülüp '9' yazıldı    hata=hayır  → hayır
  '900' yapıldı               hata=evet   → evet
```

Tablonun ikinci satırı kuralın çekirdeğidir: alan henüz terk edilmemiş ve gönderim
denenmemiş olsa bile, hata **bir kez gösterildiyse** gösterilmeye devam eder. Bunun
sonucu, ikinci çıktı bloğunun son iki satırında görünüyor — alan bir kez hatalı bulunduktan
sonra kullanıcı yazdıkça ileti anında güncelleniyor; düzeltme yapılınca kayboluyor,
yeniden bozulunca dönüyor.

İlk üç satırdaki davranış da bilinçlidir. Kullanıcı boş alana eksi işareti yazdığında
değer henüz geçersizdir ama ileti çıkmaz; girdi devam ediyordur. İleti ilk kez alan terk
edildiğinde belirir.

Bu kural, önceki dersteki `dokunulan` kümesi ile `gonderimSayisi` sayacının neden ayrı
tutulduğunu açıklıyor. İkisi de "bu alanı gösterebilir miyim" sorusunun girdisidir ve
farklı anları işaretler.

## Canlı Bölge mi, Odak mı

Sayfada beliren bir metnin ekran okuyucu tarafından okunmasının iki yolu vardır: odağı
oraya taşımak ya da metni bir **canlı bölge** içine koymak. WCAG 4.1.3 ölçütü, odak
almayan durum iletilerinin de duyurulmasını ister.

Ayrım şu soruyla yapılır: kullanıcının o an bir şey yapması gerekiyor mu?

**Odak taşıma** kullanıcıyı yönlendirir ve akışını keser. Gönderim reddedildiğinde
uygundur; kullanıcı zaten bir sonuç bekliyordur ve devam etmek için bir şey yapması
gerekir. Çıktıdaki özet kutusu `role="alert"` taşıyor; bu rol, kutunun içeriğini
belirdiği anda duyurulacak biçimde işaretler ve odak taşımayla birlikte kullanılır.

**Canlı bölge**, kullanıcının akışını kesmeden bilgi verir. Alan alan doğrulanan bir formda
her ileti için odak taşınmaz; iletiler kibar öncelikli bir bölgede birikir ve kullanıcı
yazmayı bıraktığında okunur.

Aşırı bildirim gerçek bir kusurdur. Her tuşta güncellenen bir canlı bölge, ekran okuyucuyu
sürekli konuşturur ve kullanıcının kendi yazdığını duymasını engeller. Bu yüzden alan alan
doğrulama sonuçları gecikmeli tetiklemeyle seyreltilir, gönderim sonucu ise tek seferlik
ve kesin bir duyuru olarak verilir.

## Özet

- Renk tek gösterge olamaz ve konum programlı bir bağ değildir; ileti alana ek açıklama
  bağıyla açıkça iliştirilir.
- Ek açıklama listesi hem ipucunu hem hatayı sırayla taşır; geçersizlik bildirimi iletinin
  yerine geçmez, onunla birlikte verilir ve hata düzeldiğinde kaldırılır.
- Hata özeti belge sırasına göre dizilir, hata sayısını söyler ve her maddesi ilgili alana
  bağlantı verir.
- Odak, tek hata varsa doğrudan alana, birden çok hata varsa özet kutusuna taşınır; taşıma
  yalnız kullanıcının kendi eylemine yanıt olarak yapılır.
- İleti ilk kez alan terk edildiğinde ya da gönderim denendiğinde yazılır; bir kez
  yazıldıktan sonra her değişiklikte güncellenir.
- Odağı kesmeyen bildirimler canlı bölgeyle verilir; her tuşta güncellenen bir bölge aşırı
  bildirim üretir ve gecikmeli tetiklemeyle seyreltilir.

## Sonraki Adım

Ölçüm giriş formu artık değerini tutuyor, kurallarını iki tarafta aynı biçimde uyguluyor
ve hatasını herkesin algılayabileceği biçimde sunuyor. Geriye formun sorduğu bir soru
kaldı: bu ölçümü kim giriyor? Kuzey Yamaç kayıtlarına herkes yazamaz; gözlemcinin önce
tanınması gerekir. Sonraki ders kimlik akışının istemci tarafını ele alır — sunucunun
verdiği belirtecin tarayıcıda nerede saklanacağı ve bu kararın hangi riskleri açıp hangi
riskleri kapattığı.
