---
title: 'Form Erişilebilirliği'
source: 'https://academia.sh/tr/kurslar/web-temelleri-ve-html/form-erisilebilirligi'
course: "Web'in Temelleri ve HTML"
language: tr
updated: '2026-08-17T18:09:32+00:00'
license: 'CC BY-SA 4.0'
---

# Form Erişilebilirliği

Hata iletilerinin alana bağlanması, sunucudan dönen hata özetinin kurulması, odak yönetimi ve klavyeyle kullanımın gerekleri.

Form bu noktada veri topluyor, doğruluyor ve dosya alabiliyor. Geriye bir soru kalıyor:
gönderim reddedildiğinde kullanıcı ne görüyor?

Yerleşik Doğrulama dersinde tarayıcının kendi iletisinin iki sınırı belirtilmişti: tek
seferde tek alan gösterir ve kısa sürede kaybolabilir. Sunucudan dönen hata ise yeni bir
belgeyle gelir ve kullanıcı, hangi alanların düzeltilmesi gerektiğini bulmak zorundadır.

Bu ders hata bildirimini belgede kurar. Kurulacak yapı betik gerektirmez; sunucu tarafından
üretilen işaretlemedir ve bu nedenle her koşulda çalışır.

## Bir Hata Bildiriminin Karşılaması Gerekenler

Dört gereklilik vardır ve dördü de işaretlemeyle karşılanır.

**Bulunabilirlik.** Kullanıcı, sayfanın neresine döndüğünü ve kaç hata olduğunu görmelidir.
Sayfanın ortasında tek bir kırmızı satır, o satıra bakmayan kullanıcıya hiçbir şey söylemez.

**Bağlılık.** Her ileti, ait olduğu alana bildirimle bağlanmalıdır. Alanın altına
yerleştirilmiş bir metin görsel olarak bağlıdır; erişilebilirlik ağacında bağlı değildir.

**Duyurulabilirlik.** Alana odaklanıldığında hata iletisi de duyurulmalıdır.

**Erişilebilirlik.** İletiye ulaşmak, hatanın hangi alanda olduğunu tahmin etmeyi
gerektirmemelidir.

Rengin tek başına bir bildirim olmadığı da eklenmelidir: kırmızı bir kenarlık, rengi
ayırt edemeyen kullanıcıya bir şey söylemez. Durum metinle de bildirilir.

## Sunucudan Dönen Hata Özeti

Aşağıdaki betik, sunucunun doğrulama sonrasında ürettiği belgeyi kurar. Girdi, alan
tanımları ve hata listesidir; çıktı, gönderilecek işaretlemedir.

```js
// erisilebilirlik.mjs — sunucuda uretilen hata ozeti ve alan-hata baglari
const ALANLAR = [
  { kimlik: "istasyon", ad: "istasyon", etiket: "İstasyon kodu", tur: "text" },
  { kimlik: "sicaklik", ad: "sicaklik", etiket: "Sıcaklık (°C)", tur: "number" },
  { kimlik: "eposta", ad: "eposta", etiket: "Bildirim adresi", tur: "email" },
];

const ILETI = {
  valueMissing: "Bu alan zorunludur.",
  patternMismatch: "Yalnızca küçük harf ve tire kullanın.",
  rangeOverflow: "Değer en çok 60 olabilir.",
  typeMismatch: "Geçerli bir e-posta adresi yazın.",
};

function kac(metin) {
  return metin.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

function formYaz(degerler, hatalar) {
  const satirlar = [];
  if (hatalar.length > 0) {
    satirlar.push('<div role="alert" tabindex="-1" id="hata-ozeti">');
    satirlar.push("  <h2>Form gönderilemedi: " + hatalar.length + " alan düzeltilmeli</h2>");
    satirlar.push("  <ul>");
    for (const [alanKimligi, kod] of hatalar) {
      const alan = ALANLAR.find((a) => a.kimlik === alanKimligi);
      satirlar.push(
        '    <li><a href="#' + alan.kimlik + '">' + kac(alan.etiket) + ": " + kac(ILETI[kod]) + "</a></li>",
      );
    }
    satirlar.push("  </ul>");
    satirlar.push("</div>");
  }
  satirlar.push('<form action="/olcum" method="post" novalidate>');
  for (const alan of ALANLAR) {
    const hata = hatalar.find(([kimlik]) => kimlik === alan.kimlik);
    satirlar.push('  <p><label for="' + alan.kimlik + '">' + kac(alan.etiket) + "</label>");
    const ozellikler = [
      'id="' + alan.kimlik + '"',
      'name="' + alan.ad + '"',
      'type="' + alan.tur + '"',
      'value="' + kac(degerler[alan.ad] ?? "") + '"',
    ];
    if (hata) {
      ozellikler.push('aria-invalid="true"');
      ozellikler.push('aria-describedby="' + alan.kimlik + '-hata"');
    }
    satirlar.push("     <input " + ozellikler.join(" ") + ">");
    if (hata) {
      satirlar.push(
        '     <span id="' + alan.kimlik + '-hata" class="hata">' + kac(ILETI[hata[1]]) + "</span>",
      );
    }
    satirlar.push("  </p>");
  }
  satirlar.push('  <p><button type="submit">Kaydı gönder</button></p>');
  satirlar.push("</form>");
  return satirlar.join("\n");
}

const degerler = { istasyon: "Kuzey Yamac", sicaklik: "95", eposta: "gozlem@ornek.test" };
const hatalar = [["istasyon", "patternMismatch"], ["sicaklik", "rangeOverflow"]];
console.log(formYaz(degerler, hatalar));
```

```
<div role="alert" tabindex="-1" id="hata-ozeti">
  <h2>Form gönderilemedi: 2 alan düzeltilmeli</h2>
  <ul>
    <li><a href="#istasyon">İstasyon kodu: Yalnızca küçük harf ve tire kullanın.</a></li>
    <li><a href="#sicaklik">Sıcaklık (°C): Değer en çok 60 olabilir.</a></li>
  </ul>
</div>
<form action="/olcum" method="post" novalidate>
  <p><label for="istasyon">İstasyon kodu</label>
     <input id="istasyon" name="istasyon" type="text" value="Kuzey Yamac" aria-invalid="true" aria-describedby="istasyon-hata">
     <span id="istasyon-hata" class="hata">Yalnızca küçük harf ve tire kullanın.</span>
  </p>
  <p><label for="sicaklik">Sıcaklık (°C)</label>
     <input id="sicaklik" name="sicaklik" type="number" value="95" aria-invalid="true" aria-describedby="sicaklik-hata">
     <span id="sicaklik-hata" class="hata">Değer en çok 60 olabilir.</span>
  </p>
  <p><label for="eposta">Bildirim adresi</label>
     <input id="eposta" name="eposta" type="email" value="gozlem@ornek.test">
  </p>
  <p><button type="submit">Kaydı gönder</button></p>
</form>
```

Üretilen işaretleme, dört gerekliliğin her birini karşılar.

**Özet, formdan önce durur** ve kaç alanın düzeltileceğini bir başlıkla söyler. Belgenin
başında bulunması, kullanıcının ona ek bir gezinme yapmadan varmasını sağlar; `role="alert"`
bildirimi ise bölümü ivedi duyurulacak bir bölge olarak işaretler. Belge yüklenirken
duyurulup duyurulmayacağı gerçekleştirime bağlıdır, bu nedenle özet yalnızca duyuruya
dayanmaz: konumu ve başlığıyla da bulunabilir.

**Her hata bir bağlantıdır** ve hedefi ilgili alanın kimliğidir. Bağlantı metni yalnızca
"bu alan zorunludur" demez; hangi alan olduğunu da söyler — Bağlantılar dersindeki
"bağlamdan koparıldığında anlaşılır olma" ölçütü burada da geçerlidir.

**Alanlar `aria-invalid="true"` taşır.** Bu, erişilebilirlik ağacına geçersizlik
bildirimidir ve rengin taşımadığı bilgiyi taşır.

**İleti alana `aria-describedby` ile bağlıdır.** Etiket–Alan İlişkisi dersinde kurulan
düzenek burada hata iletisi için kullanılır: alana odaklanıldığında ad, sonra tür, sonra
açıklama duyurulur.

Girilen değerlerin **korunduğuna** dikkat edilmelidir. Geçersiz değerler bile alanlara geri
yazılır; kullanıcı ne yazdığını görür ve düzeltir. Formu boşaltarak geri döndürmek, uzun
formlarda kullanıcının çalışmasını yok eder.

Kaçırma işlevi de gereklidir: alan değerleri kullanıcıdan gelir ve Etiketler, Öznitelikler
ve Varlıklar dersinde belirtildiği gibi `<`, `&` ve tırnak karakterleri kaçırılmadan
belgeye yazılırsa yapı bozulur.

## Odak Yönetimi

Özetin `tabindex="-1"` özniteliği taşıması, ona program yoluyla odaklanılabilmesi içindir.
Değer eksi bir olduğunda öge sekme sırasına girmez ama odak alabilir.

Sunucudan dönen bir belgede odak, belgenin başındadır ve özet zaten oradadır; bu durumda
ek bir işlem gerekmez. Sayfa yenilenmeden yapılan denetimlerde odağın özete taşınması
gerekir, yoksa kullanıcı yeni eklenen bölümden habersiz kalır.

Özetteki bağlantıya tıklandığında odak hedef alana gider. Bunun için alanın odaklanabilir
olması yeterlidir; girdi alanları zaten odaklanabilirdir.

`tabindex` özniteliğinin sıfırdan büyük değerleri kullanılmaz. Bu değerler ögeyi sekme
sırasının başına alır ve belgenin geri kalanının sırasını bozar; sıra, belgedeki kaynak
sırasından gelmelidir. Odak sırası ile görsel sıra ayrıştığında klavye kullanıcısı ekranda
sıçrar.

## Sayfa Yenilenmeden Bildirim

Denetim sayfa yenilenmeden yapılıyorsa, ekranda beliren bir iletinin duyurulması gerekir.
Bir **canlı bölge (live region)**, içeriği değiştiğinde ekran okuyucunun duyurduğu
bölümdür.

```html
<div role="alert" id="hata-ozeti" tabindex="-1"></div>
```

`role="alert"` bildirimi ivedi duyuru ister ve kullanıcının o anki okumasını böler; hata
bildirimleri için uygundur. Daha az ivedi bilgiler —"kayıt gönderiliyor" gibi— için
kesintisiz duyuru yapan bir canlı bölge kullanılır.

Canlı bölgenin belgede **önceden** bulunması gerekir. Belgeye sonradan eklenen bir canlı
bölgenin içeriği bazı gerçekleştirimlerde duyurulmaz; boş bir kap önceden konur ve içeriği
sonradan doldurulur.

## Klavyeyle Kullanım

Bir formun klavyeyle kullanılabilmesi, belgede hiçbir şey yapılmadığı sürece kendiliğinden
sağlanır: `input`, `select`, `textarea` ve `button` ögeleri odaklanabilirdir, sekme sırası
kaynak sırasıdır ve gönderim, alanlardan birindeyken giriş tuşuyla yapılabilir.

Bu davranışın bozulduğu yerler, dilin ögelerinin yerine başkalarının konduğu yerlerdir.
Bir `div` ögesine tıklama davranışı eklenerek yapılmış bir düğme odaklanabilir değildir,
klavyeyle basılamaz ve ekran okuyucuda düğme olarak duyurulmaz. Bunları geri kazanmak için
gereken bildirimler —odaklanabilirlik, rol, tuş işleme— `button` ögesinde zaten vardır.

Odak göstergesi de kaldırılmaz. Odaklanılan ögenin görünür bir işareti olmadığında, klavye
kullanıcısı belgenin neresinde olduğunu bilemez. Gösterge sunum katmanında
değiştirilebilir; kaldırılamaz.

## Bitmiş Form

Kurs boyunca geliştirilen belgenin son bölümü:

```html
<section aria-labelledby="bildirim">
  <h2 id="bildirim">Ölçüm Düzeltmesi Bildir</h2>

  <div role="alert" id="hata-ozeti" tabindex="-1"></div>

  <form action="/olcum/duzeltme" method="post" enctype="multipart/form-data">
    <fieldset>
      <legend>Ölçüm bilgisi</legend>

      <p><label for="istasyon">İstasyon kodu</label>
         <input id="istasyon" name="istasyon" type="text" value="kuzey-yamac"
                required maxlength="20" pattern="[a-z-]+"
                aria-describedby="istasyon-yardim">
         <span id="istasyon-yardim">Yalnızca küçük harf ve tire.</span></p>

      <p><label for="tarih">Ölçüm tarihi</label>
         <input id="tarih" name="tarih" type="date" required></p>

      <p><label for="deger">Ölçülen değer (°C)</label>
         <input id="deger" name="deger" type="number"
                required min="-60" max="60" step="0.1"></p>
    </fieldset>

    <fieldset>
      <legend>Ölçüm kaynağı</legend>
      <p><label><input type="radio" name="kaynak" value="otomatik" checked> Otomatik</label></p>
      <p><label><input type="radio" name="kaynak" value="elle"> Elle girildi</label></p>
    </fieldset>

    <fieldset>
      <legend>Ham kayıt</legend>
      <p><label for="kayit">Ölçüm dosyası</label>
         <input id="kayit" name="kayit" type="file"
                accept=".csv,text/csv" aria-describedby="kayit-yardim">
         <span id="kayit-yardim">Virgülle ayrılmış değer dosyası, en çok 5 MB.</span></p>
    </fieldset>

    <p><button type="submit">Düzeltmeyi gönder</button></p>
  </form>
</section>
```

Her alanın bir etiketi, her grubun bir adı, her kısıtın bir bildirimi ve her açıklamanın
bir bağı vardır. Belgede tek bir betik yoktur.

## Özet

- Bir hata bildirimi bulunabilir, alana bağlı, duyurulabilir ve ulaşılabilir olmalıdır;
  renk tek başına bildirim değildir.
- Sunucudan dönen hata özeti formdan önce durur, hata sayısını söyler ve her hatayı ilgili
  alana bağlanan bir bağlantı olarak sunar.
- Alanlar `aria-invalid` ile geçersizlik durumunu, `aria-describedby` ile hata iletisini
  bildirir; girilen değerler korunur ve kaçırılarak yazılır.
- `tabindex="-1"` ögeyi sekme sırasına sokmadan odaklanabilir yapar; sıfırdan büyük
  değerler belgenin odak sırasını bozar.
- Canlı bölgeler belgede önceden bulunmalıdır; sonradan eklenen bir bölgenin içeriği
  duyurulmayabilir.
- Klavyeyle kullanım, dilin kendi ögeleri kullanıldığı sürece kendiliğinden sağlanır;
  odak göstergesi değiştirilebilir ama kaldırılamaz.

## Kurs Kapanışı

Bu kurs tek bir soruyla başladı: sunucudan gelen yanıt gövdesi nedir? Yanıt bir belgedir ve
belge, baytlardan bir düğüm ağacına çevrilir.

Dört konu bu ağacı kurdu. **Web Nasıl Çalışır**, baytların karaktere, karakterlerin
sözcük birimine, birimlerin ağaca dönüşümünü izledi; ayrıştırıcının kurtarma kurallarını
ve kaynak yüklemenin bu zinciri nasıl beklettiğini gösterdi. **Belge Yapısı**, kök yapıdan tablolara
kadar belgenin iskeletini kurdu ve her etiketin bir yapı bildirimi olduğunu, görünüm
kararı olmadığını yineledi. **Anlamsal İşaretleme ve Medya**, belgeye bir üst düzey harita
ekledi ve görsel, ses, video ile gömülü içeriği erişilebilir biçimde yerleştirdi.
**Formlar**, tek yönlü belgeyi iki yönlü kıldı ve kullanıcıdan gelen verinin sunucuya nasıl
ulaştığını gerçek gövdeler üzerinden gösterdi.

Kurs boyunca izlenen ölçüm istasyonu belgesi, iki satırlık bir iskeletten bölümlenmiş,
çizelgeli, medya taşıyan ve doğrulanabilir bir form içeren bütün bir sayfaya dönüştü.
Belgede tek bir sunum kararı yoktur: hiçbir yerde bir renk, bir ölçü, bir konum
bildirilmemiştir.

Bu eksiklik bilinçlidir. İlk derste tanımlanan üç katmandan yalnızca birincisi kuruldu.
Belge şu anda bir tarayıcıda açıldığında okunabilir ve gezilebilir — çünkü tarayıcının
her öge için taşıdığı varsayılan biçemler vardır. Ancak o görünüm belgenin değil,
tarayıcının kararıdır.

Sonraki kurs, **CSS ile Görsel Sunum**, ikinci katmanı kurar: hangi ögeye hangi kuralın
uygulanacağını belirleyen seçicileri, çakışan kuralların nasıl çözüldüğünü, kutu modelini
ve akış düzenini. O kursun üzerine çalışacağı belge budur — ve iyi kurulmuş bir yapı, iyi
bir sunumun ön koşuludur: bir ögeye biçem vermek, önce o ögenin var olmasını gerektirir.
