---
title: 'Açılır Listeler'
source: 'https://academia.sh/tr/kurslar/erisilebilir-kaliplar/acilir-listeler'
course: 'Erişilebilir Bileşen Kalıpları'
language: tr
updated: '2026-08-17T18:09:33+00:00'
license: 'CC BY-SA 4.0'
---

# Açılır Listeler

Yerel seçim ögesiyle elle kurulan birleşik giriş alanının karşılaştırılması; etkin seçenek bildiriminin odaktan ayrılması, klavye sözleşmesinin sınanması ve ulaşılan durumlarda değişmezlerin denetimi.

Seçenek grubu, seçenekler ekrana sığdığında çalışır. Katalogun süzgeç panelindeki "Konu"
alanı iki yüz elliyi aşkın değer taşır; hepsi ekranda gösterilemez ve gösterilse de
gezilemez. Bu, seçim işini kapanabilen bir listeye devretmeyi gerektirir.

Kalıbın iki gerçekleştirimi vardır ve aralarındaki fark tek bir satırla özetlenemez: biri
tek bir ögeyle gelir, diğeri on ayrı bildirimi elle yazmayı gerektirir. Bu ders ikisini de
belirtir ve ikincisinin bedelini sayar.

## Kalıbın Çözdüğü Sorun

Açılır liste, **sayılabilir ama kalabalık** bir seçenek kümesinden tek bir değer
seçtirir. İki sınırı vardır. Alt sınırda, beşten az seçenek varsa liste gereksizdir: dört
ödünç süresi seçeneği bir seçenek grubuyla ekranda durur ve kullanıcı hepsini bir bakışta
görür. Üst sınırda, seçenekler kullanıcının önceden bilmediği bir kümeden geliyorsa —
kataloğun tamamındaki yazar adları gibi — liste değil arama gerekir.

Ayrıca liste, birden çok değerin seçileceği yerlerde de doğru bileşen değildir. Çok seçimli
açılır liste kalıbı kullanıcıya seçtiklerinin tamamını aynı anda göstermez; onun yerine onay
kutusu grubu ya da seçilenleri ayrı gösteren bir kalıp kullanılır.

## Yerel Öge Önce

Karşılık `select` ögesidir. Getirdikleri şunlardır: birleşik giriş alanı rolü, `label`
ögesinden hesaplanan ad, açık/kapalı durumunun bildirimi, seçili seçeneğin değer olarak
bildirimi, `optgroup` ile gruplama, klavye sözleşmesinin tamamı ve yazarak arama.

Bir bedeli vardır ve belirtimde yazılmalıdır: açılan seçenek listesinin görünümü sunum
katmanından denetlenemez. Liste, sayfanın çizim düzleminin dışında oluşturulur; yazı tipi,
renk, satır yüksekliği ve seçenek içine konacak ek bilgi tasarım kararına açık değildir.

Katalogun "Konu" alanında bu bedel ödenebilir. Sonuç sıralama ölçütü, ödünç süresi ve sayfa
büyüklüğü seçicilerinin tamamı yerel ögeyle karşılanır. Elle kurulan bir bileşen yalnızca üç
gereksinimden biri varsa seçilir: seçenek satırında ek bilgi gösterilmesi (kayıt sayısı, alt
başlık), yazarak süzme, ya da seçeneklerin sunucudan aramayla getirilmesi.

## Özel Bileşenin Bedeli

Elle kurulan bileşende yeniden üretilmesi gereken bildirimler şunlardır: rol
(`role="combobox"`), açık/kapalı durumu (`aria-expanded`), denetlenen listenin kimliği
(`aria-controls`), liste rolü (`role="listbox"`), seçenek rolleri (`role="option"`), etkin
seçeneğin bildirimi (`aria-activedescendant`), seçili seçeneğin bildirimi
(`aria-selected`), otomatik tamamlama biçimi (`aria-autocomplete`), klavye sözleşmesinin
tamamı ve süzme sonucunun duyurulması.

```html
<label for="konu">Konu</label>
<input id="konu" role="combobox" aria-expanded="true" aria-controls="konu-liste"
       aria-activedescendant="konu-2" aria-autocomplete="list" autocomplete="off">
<ul id="konu-liste" role="listbox" aria-label="Konu seçenekleri">
  <li id="konu-1" role="option" aria-selected="false">Mimarlık</li>
  <li id="konu-2" role="option" aria-selected="true">Mikrobiyoloji</li>
</ul>
<p role="status">2 konu listeleniyor</p>
```

Bu işaretlemenin ayırt edici yeri `aria-activedescendant` özniteliğidir. Klavye odağı
**alandan hiç çıkmaz**; listede gezinen şey odak değil, bir bildirimdir. Kullanıcı ok
tuşuna bastığında alandaki imleç yerinde kalır, ağaçta ise başka bir seçenek etkin olarak
duyurulur. Bu ayrım, alanın metin düzenleme davranışının korunmasını sağlar; odak gerçekten
listeye taşınsaydı yazmaya devam etmek olanaksız olurdu.

Ayrımın tasarıma yansıyan sonucu, **iki ayrı göstergenin** gerekmesidir: alanda odak halkası,
listede etkin seçenek vurgusu. İkisinden biri eksikse kullanıcı ya nerede olduğunu ya neyin
seçileceğini göremez.

## Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` | Alan tek sekme durağıdır; liste açıkken de odak alandadır. Liste kapanır ve odak dışarı çıkar. |
| `Aşağı ok` | Liste kapalıysa açar ve ilk seçeneği etkinleştirir; açıksa bir sonrakine geçer. |
| `Yukarı ok` | Liste kapalıysa açar ve son seçeneği etkinleştirir; açıksa bir öncekine geçer. |
| `Enter` | Etkin seçeneği değer yapar ve listeyi kapatır. |
| `Escape` | Liste açıksa kapatır ve değeri korur; kapalıysa alanı temizler. |
| Yazı tuşları | Listeyi açar ve seçenekleri süzer. |
| `Home` / `End` | İmleci metin içinde taşır; listeye ait değildir. |

Ok tuşlarının uçlarda **başa dönmemesi**, seçenek grubunun sözleşmesinden ayrıldığı
noktadır. Seçenek grubunda dönmek kullanıcıyı kaybettirmez, çünkü grubun tamamı ekrandadır.
Uzun bir listede dönmek, kullanıcıyı bir tuş vuruşuyla listenin öbür ucuna atar ve nerede
olduğunu okumadan anlamasını olanaksız kılar.

Aşağıdaki sınama sözleşmeyi bir durum makinesi olarak kurar, tuş dizileriyle denetler ve son
sınamada ulaşılabilen bütün durumları tarayarak üç değişmezi doğrular.

```js
// acilir.test.mjs — birlesik giris alaninin klavye sozlesmesi ve degismezleri
// Kosum: node --test acilir.test.mjs
import { test } from "node:test";
import assert from "node:assert/strict";

const KONULAR = ["Mimarlık", "Mikrobiyoloji", "Müzik Tarihi", "Matematik"];
const kucuk = (s) => s.toLocaleLowerCase("tr");

const baslangic = () => ({ odakAlanda: true, acik: false, etkin: null, metin: "", deger: null });
const secenekler = (d) => KONULAR.filter((k) => kucuk(k).startsWith(kucuk(d.metin)));

function tus(d, t) {
  const s = secenekler(d);
  if (t === "ArrowDown") {
    if (!d.acik) return { ...d, acik: true, etkin: s.length ? 0 : null };
    if (d.etkin === null) return { ...d, etkin: s.length ? 0 : null };
    return { ...d, etkin: Math.min(d.etkin + 1, s.length - 1) };   // dongu yok
  }
  if (t === "ArrowUp") {
    if (!d.acik) return { ...d, acik: true, etkin: s.length ? s.length - 1 : null };
    if (d.etkin === null) return { ...d, etkin: s.length ? s.length - 1 : null };
    return { ...d, etkin: Math.max(d.etkin - 1, 0) };
  }
  if (t === "Enter") {
    if (d.acik && d.etkin !== null)
      return { ...d, acik: false, etkin: null, metin: s[d.etkin], deger: s[d.etkin] };
    return d;
  }
  if (t === "Escape") {
    if (d.acik) return { ...d, acik: false, etkin: null };          // listeyi kapat
    return { ...d, metin: "", deger: null };                        // alani temizle
  }
  if (t === "Tab") return { ...d, acik: false, etkin: null, odakAlanda: false };
  if (t.startsWith("yaz:")) return { ...d, metin: d.metin + t.slice(4), acik: true, etkin: null };
  return d;
}

const uygula = (d, dizi) => dizi.reduce(tus, d);

test("kapaliyken asagi ok listeyi acar ve ilk secenegi etkin yapar", () => {
  const d = uygula(baslangic(), ["ArrowDown"]);
  assert.equal(d.acik, true);
  assert.equal(secenekler(d)[d.etkin], "Mimarlık");
});

test("kapaliyken yukari ok listeyi acar ve son secenegi etkin yapar", () => {
  const d = uygula(baslangic(), ["ArrowUp"]);
  assert.equal(secenekler(d)[d.etkin], "Matematik");
});

test("son secenekte asagi ok basa donmez", () => {
  const d = uygula(baslangic(), ["ArrowDown", "ArrowDown", "ArrowDown", "ArrowDown", "ArrowDown"]);
  assert.equal(d.etkin, 3);
  assert.equal(secenekler(d)[d.etkin], "Matematik");
});

test("giris tusu etkin secenegi deger yapar ve listeyi kapatir", () => {
  const d = uygula(baslangic(), ["ArrowDown", "ArrowDown", "Enter"]);
  assert.equal(d.deger, "Mikrobiyoloji");
  assert.equal(d.acik, false);
  assert.equal(d.etkin, null);
});

test("yazmak listeyi acar ve suzer, etkin secenek sifirlanir", () => {
  const d = uygula(baslangic(), ["yaz:M", "yaz:i"]);
  assert.deepEqual(secenekler(d), ["Mimarlık", "Mikrobiyoloji"]);
  assert.equal(d.etkin, null);
});

test("escape once listeyi kapatir, ikinci basista alani temizler", () => {
  const bir = uygula(baslangic(), ["yaz:M", "yaz:i", "ArrowDown", "Escape"]);
  assert.equal(bir.acik, false);
  assert.equal(bir.metin, "Mi");
  const iki = uygula(bir, ["Escape"]);
  assert.equal(iki.metin, "");
  assert.equal(iki.deger, null);
});

test("sekme tusu listeyi kapatir ve odagi alandan cikarir", () => {
  const d = uygula(baslangic(), ["ArrowDown", "Tab"]);
  assert.equal(d.acik, false);
  assert.equal(d.odakAlanda, false);
});

test("eslesme yokken liste acilir ama etkin secenek olmaz", () => {
  const d = uygula(baslangic(), ["yaz:Z"]);
  assert.deepEqual(secenekler(d), []);
  const e = uygula(d, ["ArrowDown"]);
  assert.equal(e.etkin, null);
});

test("ulasilan butun durumlarda degismezler korunur ve odak alanda kalir", () => {
  const TUSLAR = ["ArrowDown", "ArrowUp", "Enter", "Escape", "yaz:M", "yaz:i", "yaz:a"];
  const anahtar = (d) => [d.acik, d.etkin, d.metin, d.deger].join("|");
  const gorulen = new Map([[anahtar(baslangic()), baslangic()]]);
  const kuyruk = [baslangic()];
  while (kuyruk.length) {
    const d = kuyruk.shift();
    for (const t of TUSLAR) {
      const y = tus(d, t);
      if (y.metin.length > 3) continue;               // tarama uzunlugu sinirli
      if (gorulen.has(anahtar(y))) continue;
      gorulen.set(anahtar(y), y);
      kuyruk.push(y);
    }
  }
  for (const d of gorulen.values()) {
    assert.equal(d.odakAlanda, true, "odak alandan cikti");
    if (!d.acik) assert.equal(d.etkin, null, "kapali listede etkin secenek kaldi");
    if (d.etkin !== null) {
      assert.ok(d.acik, "etkin secenek varken liste kapali");
      assert.ok(d.etkin < secenekler(d).length, "etkin secenek listede yok");
    }
  }
  console.log(`  taranan durum sayisi: ${gorulen.size}`);
});
```

```
  taranan durum sayisi: 91
✔ kapaliyken asagi ok listeyi acar ve ilk secenegi etkin yapar (0.406542ms)
✔ kapaliyken yukari ok listeyi acar ve son secenegi etkin yapar (0.0605ms)
✔ son secenekte asagi ok basa donmez (0.065417ms)
✔ giris tusu etkin secenegi deger yapar ve listeyi kapatir (0.327ms)
✔ yazmak listeyi acar ve suzer, etkin secenek sifirlanir (0.311833ms)
✔ escape once listeyi kapatir, ikinci basista alani temizler (0.072417ms)
✔ sekme tusu listeyi kapatir ve odagi alandan cikarir (0.045125ms)
✔ eslesme yokken liste acilir ama etkin secenek olmaz (0.05225ms)
✔ ulasilan butun durumlarda degismezler korunur ve odak alanda kalir (2.111542ms)
ℹ tests 9
ℹ suites 0
ℹ pass 9
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 36.581875
```

Süre değerleri koşumdan koşuma değişir. Taranan durum sayısı ise yazma uzunluğunun üç
karakterle sınırlanmasına bağlıdır; sınır kaldırıldığında durum uzayı sonsuzdur, çünkü
kullanıcı istediği kadar karakter yazabilir.

## Değişmezlerin Okunması

Son sınama üç kural denetliyor ve üçü de ekranla ağacın ayrışabildiği yerlerdir.

**Kapalı listede etkin seçenek kalamaz.** `aria-activedescendant` değeri temizlenmeden
liste kapatılırsa, ağaçta var olmayan bir seçenek etkin görünür. Bu, kullanıcının listeyi
kapattıktan sonra hâlâ liste içinde olduğunu sanmasına yol açar.

**Etkin seçenek listede bulunmalıdır.** Süzme sonrası liste kısaldığında eski dizin
geçersizleşir. Sınamada bu, `yaz:` tuşlarının etkin seçeneği sıfırlamasıyla önleniyor;
sıfırlanmadığında bildirim var olmayan bir kimliğe işaret eder ve sessizce düşer.

**Odak alandan çıkmaz.** Doksan bir durumun tamamında koşul sağlanıyor. Bu, kalıbın tanımı
gereğidir: odağın listeye taşındığı bir gerçekleştirim `aria-activedescendant` kalıbı değil,
başka bir kalıptır ve o durumda yazarak süzme olanağı kaybedilir.

## Ölçülebilir Kısıtlar

**4.1.2 Ad, Rol, Değer.** Alanın adı, birleşik giriş alanı rolü, açık/kapalı durumu ve
seçili değeri program yoluyla okunabilmelidir. Elle kurulan bileşende dördü de ayrı ayrı
yazılır.

**2.4.7 Odak Görünürlüğü.** Gerçek odak alandadır; göstergesi alanda bulunmalıdır. Etkin
seçenek vurgusu bu göstergenin yerine geçmez, ona eklenir.

**1.4.11 Metin Dışı Kontrast.** Etkin seçenek vurgusu, komşu satırlardan en az 3:1 ile
ayrılmalıdır. Yalnızca açık gri bir yüzeyle kurulan vurgu bu ölçüyü karşılamaz.

**1.4.13 Üzerine Gelme veya Odaklanmayla Beliren İçerik.** Açılan liste odakla beliren bir
içeriktir ve üç koşulu karşılamalıdır: `Escape` ile kapatılabilmeli, imleç listenin üzerine
götürülebilmeli ve kullanıcı bir eylem yapmadan kaybolmamalıdır.

**Süzme sonucunun duyurulması.** Yazarak süzülen bir listede kaç seçeneğin kaldığı, kibar
öncelikli bir durum bölgesiyle bildirilir. Bildirim her tuş vuruşunda değil, yazma durduktan
sonra gönderilir; aksi durumda duyuru kuyruğu kullanıcının yazdığını duymasını engeller.

## Sık Yapılan Hata

**Odağın listeye taşınması.** Saptaması, liste açıkken yazmayı denemektir: yazılan karakter
alana gitmiyorsa odak kaymıştır. Bu kalıpta düzeltme, odağı geri taşımak değil,
`aria-activedescendant` bildirimine geçmektir.

**Açık/kapalı durumunun güncellenmemesi.** Liste ekranda görünürken `aria-expanded`
değerinin `false` kalması, ağacın ekranla çelişmesidir. Saptaması bir teklik ve tutarlılık
denetimidir: listenin görünürlüğüyle bildirilen durum karşılaştırılır.

**Yerel ögenin görünüş uğruna bırakılması.** Elle kurulan bileşenin bedeli on bildirimdir
ve her biri ayrı bir hata kaynağıdır. Saptaması belirtim aşamasındadır: seçenek satırında ek
bilgi, yazarak süzme ya da sunucudan getirme gereksinimlerinden hiçbiri yoksa karar yerel
ögedir.

## Özet

- Açılır liste, sayılabilir ama ekrana sığmayan seçenek kümeleri içindir; beşten az seçenek
  için seçenek grubu, önceden bilinmeyen kümeler için arama kullanılır.
- `select` ögesi rolü, adı, durumu, değeri ve klavye sözleşmesini birlikte getirir; bedeli,
  açılan listenin görünümünün sunum katmanından denetlenememesidir.
- Elle kurulan bileşen on ayrı bildirimi yeniden üretir; yalnızca satır içi ek bilgi,
  yazarak süzme ya da sunucudan getirme gerektiğinde seçilir.
- `aria-activedescendant` kalıbında odak alandan çıkmaz; listede gezinen bildirimdir ve bu
  yüzden iki ayrı görsel gösterge gerekir.
- Ok tuşları uçlarda başa dönmez; dönme davranışı yalnızca tamamı ekranda duran seçenek
  gruplarına aittir.
- Kapalı listede etkin seçenek bildirimi kalamaz ve etkin seçenek her zaman süzülmüş listede
  bulunmalıdır; süzme sonrası bildirimin sıfırlanması bunu sağlar.

## Sonraki Adım

Buraya kadarki dört bileşen tek başına ele alındı: bir düğme, bir alan, bir grup, bir liste.
Ödünç alma formu ise bunların hepsini bir arada taşır ve yeni sorular çıkarır. Alanlar hangi
ölçüte göre gruplanır? Form üç adıma bölündüğünde adımın değiştiği nasıl bildirilir ve odak
nereye gider? Gönderim başarısız olduğunda kullanıcı yazdıklarını kaybeder mi? Sonraki ders
formu bir bütün olarak belirtir ve tek tek doğru olan bileşenlerin bir arada yanlış
çalışabildiği yerleri gösterir.
