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

# Menüler ve Açılır Paneller

Menü rolüyle açılır panelin ayrımı, üç kurulumun odak kaçağı ve kapanma davranışı bakımından taranması, odak dönüş noktasının ve aç-kapa döngüsünün doğrulanması.

Sekme ve akordeon kalıplarında açılan içerik sayfanın akışı içinde kaldı: bir yer kapladı,
altındaki içeriği aşağı itti ve odak kendi başına ilerledi. Katalogun üst çubuğundaki
"Hesabım" denetimi farklı davranır — açtığı liste sayfanın üstünde durur, altındaki içeriği
örter ve dışına tıklandığında kapanır.

Bu, kapanmanın bir davranış olarak belirtilmesi gereken ilk kalıptır. Kapanma birden çok
yolla olur ve her yolun ardından odağın nereye gideceği ayrı bir karardır. Ders iki kalıbı
birlikte ele alır, çünkü ekranda birbirine benzemelerine karşın sözleşmeleri ayrıdır.

## İki Kalıp, İki Sözleşme

**Açılır panel**, bir düğmenin gösterip gizlediği sıradan içeriktir. İçinde bağlantı,
düğme, alan, hatta bir form bulunabilir. Akordeonla aynı bildirimi kullanır —
`aria-expanded` — ve tek farkı içeriğin sayfanın üstünde durmasıdır.

**Menü**, uygulama benzeri komut listeleridir ve kendi rol kümesiyle kurulur: `role="menu"`,
`role="menuitem"`, `role="menuitemcheckbox"`. Bu rollerin bir sözleşmesi vardır ve
sözleşme kısıtlayıcıdır: menü içinde yalnız menü ögeleri bulunur, gezinme ok tuşlarıyla
yapılır ve menünün tamamı tek bir sekme durağıdır.

Karar ölçütü içeriktir. "Hesabım" denetiminin açtığı liste — "Ödünçlerim", "Ayarlar",
"Çıkış" — sıradan bağlantılardan oluşur ve bir gezinme listesidir; kalıbı **açılır paneldir**.
Menü rolü, sonuç tablosundaki "Dışa aktar" düğmesinin açtığı komut listesi gibi, ögeleri
gezinme değil eylem olan listelerde kullanılır.

Yanlış seçim sessiz bir maliyet üretir. Bağlantılardan oluşan bir listeye menü rolü
yazıldığında bağlantılar ağaçta menü ögesi olarak bildirilir; kullanıcının bağlantı
listesinden onlara ulaşması olanaksızlaşır ve "yeni sekmede aç" beklentisi karşılanmaz.

## Yerel Öge Önce

İkisinin de yerel karşılığı yoktur. Açan denetim `button` ögesidir; açılır panelde
`aria-expanded` ve `aria-controls` yazılır, menüde ayrıca `aria-haspopup="menu"` bildirilir.

```html
<button id="hesabim" aria-expanded="false" aria-controls="hesap-paneli">Hesabım</button>
<div id="hesap-paneli" hidden>
  <ul>
    <li><a href="/hesap/oduncler">Ödünçlerim</a></li>
    <li><a href="/hesap/ayarlar">Ayarlar</a></li>
  </ul>
  <form action="/cikis" method="post"><button type="submit">Çıkış</button></form>
</div>
```

Panelin içeriği sıradan işaretlemedir; liste liste, form form olarak kalır. Bu, kalıbın
menüye yeğlenmesinin gerekçesidir: hiçbir yapı anlamı elle yeniden üretilmez.

## Kapanma Yolları ve Odak Dönüşü

Kapanma dört yolla olur: açan düğmenin yeniden çalıştırılması, `Escape` tuşu, panelin
dışına tıklanması ve panel içinde bir ögenin seçilmesi.

Odağın nereye gideceği kapanma yoluna göre değişir ve kural şudur: **kullanıcının başlattığı
kapanışta odak açan düğmeye döner.** `Escape` tuşuyla ve düğmenin yeniden çalıştırılmasıyla
kapanışta bu geçerlidir. Panel içindeki bir bağlantı seçildiğinde ise gezinme olur ve odak
yeni sayfaya gider; dönüş gereksizdir.

Dışarı tıklama özel bir durumdur. Tıklayan kullanıcı odağı zaten tıkladığı yere taşımıştır;
odağı açan düğmeye döndürmek onu tıkladığı yerden geri çeker. Kural: dışarı tıklamada odak
yalnız **hiçbir yere yerleşmediyse** açan düğmeye taşınır.

```js
// menu.mjs — acilir panelde odak kusatmasi, kapanma ve odak donus noktasinin denetimi

const ARKA = ["a#katalog", "button#hesabim", "input#ara"];
const ICERIK = ["a#odunclerim", "a#ayarlar", "button#cikis"];
const TETIK = "button#hesabim";
const TUSLAR = ["Tab", "Shift+Tab", "ArrowDown", "ArrowUp", "Escape", "Enter", "DisariTik"];

// tur: "panel" (acilir panel) | "menu" (menu rolu) | "kusurlu"
function makine(tur) {
  const arkaGit = (i, yon) => ARKA[(i + yon + ARKA.length) % ARKA.length];
  return (d, t) => {
    if (!d.acik) {
      const i = ARKA.indexOf(d.odak);
      if (t === "Tab") return { acik: false, odak: arkaGit(i, 1) };
      if (t === "Shift+Tab") return { acik: false, odak: arkaGit(i, -1) };
      if ((t === "Enter" || t === "ArrowDown") && d.odak === TETIK)
        return { acik: true, odak: ICERIK[0] };
      return d;
    }
    const j = ICERIK.indexOf(d.odak);
    if (tur === "menu") {
      if (t === "ArrowDown") return { acik: true, odak: ICERIK[(j + 1) % ICERIK.length] };
      if (t === "ArrowUp")
        return { acik: true, odak: ICERIK[(j + ICERIK.length - 1) % ICERIK.length] };
      if (t === "Tab") return { acik: false, odak: arkaGit(ARKA.indexOf(TETIK), 1) };
      if (t === "Shift+Tab") return { acik: false, odak: TETIK };
      if (t === "Escape") return { acik: false, odak: TETIK };
      if (t === "DisariTik") return { acik: false, odak: TETIK };
      return d;
    }
    if (tur === "panel") {
      if (t === "Tab")
        return j === ICERIK.length - 1
          ? { acik: false, odak: arkaGit(ARKA.indexOf(TETIK), 1) }
          : { acik: true, odak: ICERIK[j + 1] };
      if (t === "Shift+Tab")
        return j === 0 ? { acik: true, odak: TETIK } : { acik: true, odak: ICERIK[j - 1] };
      if (t === "Escape") return { acik: false, odak: TETIK };
      if (t === "DisariTik") return { acik: false, odak: TETIK };
      return d;
    }
    // kusurlu: escape yok, disari tikta odak kayboluyor, Tab arka plana kaciyor
    if (t === "Tab")
      return j === ICERIK.length - 1
        ? { acik: true, odak: ARKA[0] }
        : { acik: true, odak: ICERIK[j + 1] };
    if (t === "Shift+Tab") return { acik: true, odak: ICERIK[Math.max(j - 1, 0)] };
    if (t === "DisariTik") return { acik: false, odak: "(belge koku)" };
    return d;
  };
}

const anahtar = (d) => (d.acik ? "acik" : "kapali") + "|" + d.odak;

function denetle(tur) {
  const sonraki = makine(tur);
  const bas = { acik: false, odak: TETIK };
  const gorulen = new Map([[anahtar(bas), bas]]);
  const kuyruk = [bas];
  const kapanislar = [];       // [oncekiDurum, tus, sonrakiDurum]
  while (kuyruk.length) {
    const d = kuyruk.shift();
    for (const t of TUSLAR) {
      const y = sonraki(d, t);
      if (d.acik && !y.acik) kapanislar.push([d, t, y]);
      if (gorulen.has(anahtar(y))) continue;
      gorulen.set(anahtar(y), y);
      kuyruk.push(y);
    }
  }
  const durumlar = [...gorulen.values()];
  const escapeIle = kapanislar.filter(([, t]) => t === "Escape");
  return {
    durum: durumlar.length,
    kacak: durumlar.some((d) => d.acik && ARKA.includes(d.odak) && d.odak !== TETIK),
    escapeCikisi: escapeIle.length > 0,
    escapeDonusu: escapeIle.length > 0 && escapeIle.every(([, , y]) => y.odak === TETIK),
    odakKaybi: kapanislar.some(([, , y]) => !ARKA.includes(y.odak) && y.odak !== TETIK),
    dongu: anahtar(sonraki(sonraki(bas, "Enter"), "Escape")) === anahtar(bas),
    icDurak: tur === "menu" ? 1 : ICERIK.length,
  };
}

const im = (b) => (b ? "var" : "yok");
console.log("kurulum".padEnd(10) + "durum".padEnd(8) + "ic durak".padEnd(10) +
  "arka plana kacak".padEnd(18) + "Escape cikisi".padEnd(15) + "tetige donus".padEnd(14) +
  "odak kaybi".padEnd(12) + "ac-kapa dongusu");
for (const tur of ["panel", "menu", "kusurlu"]) {
  const s = denetle(tur);
  console.log(tur.padEnd(10) + String(s.durum).padStart(5).padEnd(8) +
    String(s.icDurak).padStart(4).padEnd(10) +
    im(s.kacak).padEnd(18) + im(s.escapeCikisi).padEnd(15) +
    im(s.escapeDonusu).padEnd(14) + im(s.odakKaybi).padEnd(12) +
    (s.dongu ? "kapali" : "ACIK"));
}

// Dogru kurulumda odak izi
console.log("\nacilir panel kurulumunda odak izi:");
let d = { acik: false, odak: TETIK };
const sonraki = makine("panel");
console.log("  " + "baslangic".padEnd(12) + "kapali".padEnd(9) + "odak: " + d.odak);
for (const t of ["Enter", "Tab", "Tab", "Escape", "Tab"]) {
  d = sonraki(d, t);
  console.log("  " + t.padEnd(12) + (d.acik ? "acik" : "kapali").padEnd(9) + "odak: " + d.odak);
}
```

```
kurulum   durum   ic durak  arka plana kacak  Escape cikisi  tetige donus  odak kaybi  ac-kapa dongusu
panel         8      3      yok               var            var           yok         kapali
menu          6      1      yok               var            var           yok         kapali
kusurlu       8      3      var               yok            yok           var         ACIK

acilir panel kurulumunda odak izi:
  baslangic   kapali   odak: button#hesabim
  Enter       acik     odak: a#odunclerim
  Tab         acik     odak: a#ayarlar
  Tab         acik     odak: button#cikis
  Escape      kapali   odak: button#hesabim
  Tab         kapali   odak: input#ara
```

## Bulguların Okunması

**İç durak sütunu iki kalıbı ayırıyor.** Açılır panelde içerik doğal sekme sırasındadır ve üç
durak tutar; menüde dönen sekme durağı vardır ve tamamı tek durak sayılır. Bu, işaretlemeye
bakmadan hangi kalıbın kurulduğunu söyleyen ölçüdür.

**Aç-kapa döngüsü kapalı olmalıdır.** Son sütun, düğmeye odaklanmış bir kullanıcının paneli
açıp `Escape` ile kapattığında **birebir aynı duruma** dönüp dönmediğini sınıyor. Doğru iki
kurulumda döngü kapalı; kusurlu kurulumda açık, çünkü `Escape` tuşu hiçbir şey yapmıyor.
Döngünün kapalı olması, kullanıcının paneli yanlışlıkla açtığında kaybettiği hiçbir şey
olmadığı anlamına gelir.

**Kusurlu kurulum üç kusuru birden taşıyor.** Panel açıkken `Tab` tuşu arka plandaki
denetimlere kaçıyor: kullanıcı görmediği, panelin altında kalan ögelere odaklanır.
`Escape` tuşuyla çıkış yolu yok — bu, 2.1.2 ölçütünün konusudur, ancak burada tam bir
tuzak değildir, çünkü `Tab` ile kaçış vardır. Dışarı tıklamada odak belgenin köküne
düşüyor ve kullanıcı dolaşıma en baştan başlıyor.

**Menü altı, panel sekiz duruma ulaşıyor.** Fark, panelin açıkken açan düğmeye geri
dönebilmesinden gelir: `Shift+Tab` tuşuyla ilk ögeden düğmeye çıkılır ve panel açık kalır.
Menüde aynı tuş menüyü kapatır. İkisi de tanımlıdır; belirtimde hangisinin seçildiği
yazılır.

## Klavye Sözleşmesi

Açılır panel:

| Tuş | Davranış |
|---|---|
| `Enter` / `Boşluk` (düğmede) | Paneli açar; odak ilk ögeye taşınır. |
| `Aşağı ok` (düğmede) | Paneli açar ve odağı ilk ögeye taşır. |
| `Tab` | Panel ögeleri arasında ilerler; son ögeden sonra paneli kapatır. |
| `Escape` | Paneli kapatır; odak açan düğmeye döner. |

Menü:

| Tuş | Davranış |
|---|---|
| `Enter` / `Boşluk` / `Aşağı ok` (düğmede) | Menüyü açar; odak ilk ögeye taşınır. |
| `Aşağı ok` / `Yukarı ok` | Ögeler arasında gezer; uçlarda başa döner. |
| `Home` / `End` | İlk ve son ögeye gider. |
| `Escape` | Menüyü kapatır; odak açan düğmeye döner. |
| `Tab` | Menüyü kapatır ve odağı sonraki sayfa durağına taşır. |

Menü kalıbında `Tab` tuşunun menüyü kapatması bir kısıt değil, tanımın parçasıdır: menü
tek durak olduğu için içinde ilerleyecek bir sekme sırası yoktur.

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

**2.1.2 Klavye Tuzağı Yok.** Panel açıkken klavyeyle çıkılabilmelidir. `Escape` tuşu bu
çıkışın en kısa yoludur ve her iki kalıpta da zorunlu sayılır.

**1.4.13 Üzerine Gelme veya Odaklanmayla Beliren İçerik.** Panel imleçle açılıyorsa üç
koşul gerekir: `Escape` ile kapatılabilmeli, imleç panelin üzerine götürülebilmeli ve
kullanıcı bir eylem yapmadan kaybolmamalıdır. Üzerine gelindiğinde açılıp imleç kaydığında
kapanan menüler bu ölçütü kaldırır; ayrıca dokunmatik girdide üzerine gelme durumu hiç
oluşmadığı için kalıp tıklamayla da açılabilmelidir.

**2.4.7 Odak Görünürlüğü.** Panel içindeki ögelerin odak göstergesi, panelin kendi yüzeyi
üzerinde de yeterli karşıtlık taşımalıdır; sayfa yüzeyine göre ayarlanmış tek renkli bir
halka panel yüzeyinde kaybolabilir.

**1.4.11 Metin Dışı Kontrast.** Panelin sınırı, altındaki içerikle karışmayacak biçimde 3:1
ölçüsünü karşılamalıdır; panel yalnız gölgeyle ayrılıyorsa sınır görünmez.

**2.5.8 Hedef Boyutu.** Panel ögeleri 24 piksel ölçüsüne girer; dar açılır listelerde satır
yüksekliği bu ölçüyü belirler.

## Sık Yapılan Hata

**Bağlantı listesine menü rolü yazılması.** Saptaması bir yapı denetimidir: `role="menu"`
taşıyan ögenin altında menü ögesi olmayan bir çocuk varsa kalıp yanlış kurulmuştur.

**Kapanışta odağın bırakılması.** Panel kapandığında odak silinen bir ögenin üzerindeyse
belgenin köküne düşer. Saptaması, denetimdeki döngü sınamasıdır: aç-kapa dizisi başlangıç
durumuna dönmüyorsa odak yönetimi eksiktir.

**`Escape` tuşunun yalnız panele bağlanması.** Kullanıcı odağı açan düğmeye geri aldığında
`Escape` tuşu çalışmıyorsa, kapanmanın bir yolu eksilir. Tuş, düğme ve panelin ortak
atasına bağlanır.

**Panelin ağaçtan gizlenmemesi.** Sunum katmanında gizlenen bir panel ağaçta kalır ve
kullanıcı, kapalı olduğunu sandığı listenin ögelerini okur.

## Özet

- Açılır panel sıradan içeriği gösterip gizler; menü, ögeleri komut olan listeler içindir ve
  kısıtlayıcı bir rol sözleşmesi taşır.
- Bağlantılardan oluşan bir listeye menü rolü yazmak, bağlantıları ağaçta menü ögesine
  çevirir ve bağlantı listesinden ulaşılmasını engeller.
- Açılır panelin içeriği doğal sekme sırasındadır; menü dönen sekme durağı kullanır ve tek
  durak tutar. Bu ölçü iki kalıbı ayırt eder.
- Kullanıcının başlattığı her kapanışta odak açan düğmeye döner; panel içinden gezinilerek
  yapılan kapanışta dönüş gerekmez.
- Aç-kapa döngüsü kapalı olmalıdır: düğmede başlayan bir aç–kapat dizisi birebir aynı duruma
  dönmelidir.
- Panel açıkken arka plandaki denetimlere odak kaçmamalı ve `Escape` her koşulda bir çıkış
  yolu bırakmalıdır.

## Sonraki Adım

Buraya kadarki kapsayıcılar içeriği gösterip gizledi; hiçbiri kullanıcıya **nerede
olduğunu** söylemedi. Katalogda ise konum bir bilgidir: kullanıcı üç düzey aşağıda bir konu
dalındadır ve sonuç listesinin dördüncü sayfasındadır. İki bileşen bu bilgiyi taşır ve
ikisi de aynı bildirime dayanır — bulunulan yerin diğerlerinden ayrılması. Sonraki ders
kırıntı yolunu ve sayfalamayı belirtir; konumun nasıl bildirileceğini ve sayfa değişiminin
neden bir odak kararı gerektirdiğini ele alır.
