Flutter Accessibility (a11y): Herkes İçin Kullanılabilir Uygulamalar

12 dakika okuma9 Şubat 2026Güncellendi: 9 Mar 2026
Flutter accessibilityFlutter a11yFlutter SemanticsFlutter color contrastFlutter text scalingFlutter screen readeraccessible mobile appFlutter UX

# Flutter Erişilebilirlik (a11y): Herkes İçin Uygulama Geliştirmek

Erişilebilirlik, yayına almadan önce eklenen kozmetik bir iyileştirme değil — ürün kalitesinin ta kendisidir. Flutter'da a11y'den bahsettiğimizde, ekran okuyucu kullanan, az gören, motor beceri kısıtlılığı olan ve daha birçok farklı ihtiyaca sahip kullanıcının uygulamayı algılayabilmesini, gezinebilmesini ve etkileşim kurabilmesini kastediyoruz.

Bu rehberde Flutter'ın sunduğu araçları, hemen uygulayabileceğiniz kod örneklerini ve denetimlerde en sık karşılaştığım hataları paylaşıyorum.

Erişilebilirlik Neden Önemli?

Dünya nüfusunun yaklaşık %15'i bir tür engelle yaşıyor. Etik boyutun ötesinde, erişilebilir uygulamalar daha geniş bir kitleye ulaşır ve ADA (ABD), EAA (AB), BITV (Almanya) gibi yasal gereklilikleri karşılar. Mühendislik açısından bakarsak, erişilebilir kod genellikle daha iyi yapılandırılmış koddur: net semantikler widget'ları test etmeyi, yeniden düzenlemeyi ve ekibe yeni katılan geliştiricilerin kodu anlamasını kolaylaştırır.

Semantik Ağaç (Semantics Tree)

Flutter kendi piksellerini çizdiği için işletim sistemi, UIKit veya Android Views'ta olduğu gibi native görünümlerden anlam çıkaramaz. Bunun yerine Flutter, kullanıcı arayüzünü yardımcı teknolojilere tanımlayan paralel bir Semantik ağaç tutar.

Anlam taşıyan her widget, bu ağaca bir düğüm eklemelidir. `Text`, `ElevatedButton`, `Checkbox` gibi yerleşik widget'lar bunu otomatik yapar. Özel widget'larınız için semantiği kendiniz sağlamanız gerekir.

Semantics Widget'ı

`Semantics`, ana aracınızdır. Anlam taşıyan ancak yerleşik semantiği olmayan widget'ları sarmalayın:

dart
Semantics(
  label: class="code-string">'Mevcut kullanıcının profil fotoğrafı',
  image: true,
  child: CircleAvatar(
    backgroundImage: NetworkImage(user.avatarUrl),
  ),
)

Etkileşimli özel widget'lar için kullanılabilir aksiyonları tanımlayın:

dart
Semantics(
  label: class="code-string">'Bu makaleyi favorilere ekle',
  button: true,
  onTap: () => _toggleFavorite(),
  child: GestureDetector(
    onTap: _toggleFavorite,
    child: Icon(
      isFavorite ? Icons.star : Icons.star_border,
      color: isFavorite ? Colors.amber : Colors.grey,
    ),
  ),
)

MergeSemantics

Birden fazla widget tek bir mantıksal birim oluşturduğunda, ekran okuyucu bunları tek bir öğe olarak duyurmalıdır. Grubu `MergeSemantics` ile sarmalayın:

dart
MergeSemantics(
  child: Row(
    children: [
      Icon(Icons.location_on),
      SizedBox(width: class="code-number">4),
      Text(class="code-string">'İstanbul, Türkiye'),
    ],
  ),
)

Bu sarmalama olmadan TalkBack, "location_on" ve ardından "İstanbul, Türkiye" şeklinde iki ayrı öğe olarak duyurur ki bu kafa karıştırıcıdır.

ExcludeSemantics

Bilgi değeri taşımayan dekoratif öğeler, ekran okuyucu çıktısını karmaşıklaştırmamaları için semantik ağaçtan gizlenmelidir:

dart
ExcludeSemantics(
  child: Image.asset(
    class="code-string">'assets/dekoratif_ayirici.png',
  ),
)

Aynı etkiyi `Semantics(excludeSemantics: true, child: ...)` ile de sağlayabilirsiniz, ancak `ExcludeSemantics` widget ağacında daha okunabilir durur.

Özel Semantik Aksiyonlar

Karmaşık widget'lar için — kaydırarak kapatılabilen kartlar, sıfırdan oluşturulmuş slider'lar, sürükle-bırak kutucukları — özel semantik aksiyonlar tanımlayabilirsiniz:

dart
Semantics(
  label: class="code-string">'Sipariş öğesi: Espresso',
  customSemanticsActions: {
    CustomSemanticsAction(label: class="code-string">'Siparişten kaldır'): _removeItem,
    CustomSemanticsAction(label: class="code-string">'Miktarı artır'): _increaseQty,
    CustomSemanticsAction(label: class="code-string">'Miktarı azalt'): _decreaseQty,
  },
  child: _buildOrderTile(),
)

Bu sayede ekran okuyucu kullanıcıları, gören kullanıcıların kaydırma hareketleri veya dokunma hedefleriyle elde ettiği işlevlerin aynısına sahip olur.

Renk Kontrastı ve Tipografi

WCAG Kontrast Gereksinimleri

Web İçerik Erişilebilirlik Kılavuzları (WCAG 2.1), mobil için geçerli iki uyumluluk seviyesi tanımlar:

  • **AA** — normal metin için minimum **4.5:1**, büyük metin (18 pt+ veya 14 pt+ kalın) için **3:1** kontrast oranı.
  • **AAA** — normal metin için **7:1**, büyük metin için **4.5:1**.
  • Erişilebilirlik denetimi yaptığım uygulamalarda en sık karşılaştığım sorun, ikincil metin ve yer tutucu (placeholder) ipuçlarında yetersiz kontrasttır. Beyaz üzerine açık gri en yaygın hatalı kombinasyondur.

    Kontrastı Pratikte Kontrol Etme

    Tasarım aşamasında [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) gibi araçları kullanın. Flutter'da erişilebilirlik denetçi katmanını etkinleştirebilirsiniz:

    dart
    MaterialApp(
      showSemanticsDebugger: true,
      class=class="code-string">"code-comment">// ...
    )

    Bu, çizilen arayüzü semantik ağacın görselleştirmesiyle değiştirir; eksik etiketler ve yapısal sorunlar anında görünür hale gelir.

    Yalnızca Renge Güvenmeyin

    Durumu belirtmek için rengi tek gösterge olarak asla kullanmayın. Kırmızı/yeşil durum noktası, protanopi (renk körlüğü) olan biri için anlamsızdır. Rengi her zaman bir şekil, ikon veya metin etiketiyle eşleştirin:

    dart
    Row(
      children: [
        Icon(
          isOnline ? Icons.check_circle : Icons.cancel,
          color: isOnline ? Colors.green : Colors.red,
        ),
        SizedBox(width: class="code-number">8),
        Text(isOnline ? class="code-string">'Çevrimiçi' : class="code-string">'Çevrimdışı'),
      ],
    )

    Metin Ölçeklendirme ve Dinamik Yazı Tipi

    Kullanıcılar okunabilirlik için sistem yazı tipi boyutunu artırabilir. Flutter, `Text` widget'larında `MediaQuery.textScaleFactor` değerini varsayılan olarak dikkate alır, ancak özel düzenler metin büyüdüğünde sıklıkla bozulur.

    Ölçeklenmiş Metin İçin Tasarım

    Uygulamanızı 1.0, 1.5 ve 2.0 ölçek faktörlerinde test edin. Sabit yükseklik, tek satır varsayımı veya dar dolgu kullanan düzenler taşacaktır.

    dart
    class=class="code-string">"code-comment">// Kötü: ölçeklenmiş metni kırpacak sabit yükseklik
    SizedBox(
      height: class="code-number">48,
      child: Text(class="code-string">'Ayarlar'),
    )
    
    class=class="code-string">"code-comment">// İyi: içeriğin büyümesine izin verin
    Padding(
      padding: EdgeInsets.symmetric(vertical: class="code-number">12),
      child: Text(class="code-string">'Ayarlar'),
    )

    Kullanıcı Tercihlerine Saygı Gösterme

    Metin ölçeklendirmesini sınırlamanız kesinlikle gerekiyorsa (örneğin küçük bir rozet widget'ında), bunu açıkça yapın ve nedenini belgeleyin:

    dart
    MediaQuery(
      data: MediaQuery.of(context).copyWith(
        textScaler: TextScaler.linear(
          MediaQuery.of(context).textScaler.scale(class="code-number">1.0).clamp(class="code-number">1.0, class="code-number">1.3),
        ),
      ),
      child: _BadgeWidget(),
    )

    Erişilebilirlik denetimi yaptığım uygulamalarda, metin ölçeklendirme sorunlarının çoğunun basit etiketlerde değil, veri tabloları, alt navigasyon çubukları ve input süslemeleri gibi karmaşık düzenlerde ortaya çıktığını görüyorum. Bu alanları her zaman elle doğrulayın.

    Ekran Okuyucu Testi — Pratik Rehber

    Otomatik araçlar erişilebilirlik sorunlarının yalnızca yaklaşık %30'unu yakalar. Ekran okuyucu ile elle test vazgeçilmezdir.

    iOS — VoiceOver

  • **Ayarlar > Erişilebilirlik > VoiceOver** yolunu izleyip etkinleştirin.
  • Geliştirme sırasında iOS Simülatöründe **Cmd + F5** kısayolunu kullanarak VoiceOver'ı açıp kapatın.
  • Sonraki öğeye geçmek için sağa kaydırın. Etkinleştirmek için çift dokunun.
  • Eksik etiketleri, kafa karıştıran sıralamayı ve gereksiz duyuruları dinleyin.
  • Android — TalkBack

  • **Ayarlar > Erişilebilirlik > TalkBack** yolunu izleyip etkinleştirin.
  • Android Emülatöründe, TalkBack'i programatik olarak etkinleştirmek için **adb shell settings put secure enabled_accessibility_services com.google.android.marvin.talkback/.TalkBackService** komutunu kullanın.
  • Gezinmek için sağa kaydırın. Etkinleştirmek için çift dokunun.
  • Okuma sırasına dikkat edin — görsel olarak yukarıdan aşağıya, soldan sağa akışı takip etmelidir.
  • Nelere Dikkat Etmeli?

  • **Eksik etiketler**: Ekran okuyucu açıklama olmadan sadece "düğme" der.
  • **Gereksiz etiketler**: Okuyucu "düğme, Favori düğmesi" der — rol iki kez duyurulur.
  • **Yanlış öğe gruplaması**: İlişkili bilgiler birçok durağa bölünür, gezinmeyi yavaşlatır.
  • **Bozuk odak sırası**: Okuyucu ekranın beklenmedik bölümlerine atlar.
  • **Eksik durum değişikliği duyuruları**: Bir anahtar değiştirildikten sonra yeni durum duyurulmaz.
  • Dinamik Değişiklikleri Duyurma

    İçerik bir gezinme olayı olmadan güncellendiğinde — snackbar belirir, sayaç artar, form doğrulama yapar — değişikliği duyurmak için `SemanticsService` kullanın:

    dart
    import class="code-string">'package:flutter/semantics.dart';
    
    SemanticsService.announce(class="code-string">'Ürün sepete eklendi. Sepet toplamı: class="code-number">3 ürün.', TextDirection.ltr);

    Odak Yönetimi ve Klavye Navigasyonu

    Mantıksal Odak Sırası

    Odak, ekranda tahmin edilebilir bir sırayla ilerlemelidir. Flutter genellikle widget ağacı sırasını takip eder, ancak overlay'ler, diyaloglar ve karmaşık düzenler bu sırayı bozabilir.

    Navigasyonu kontrol etmek için `FocusTraversalGroup` ve `FocusTraversalOrder` kullanın:

    dart
    FocusTraversalGroup(
      policy: OrderedTraversalPolicy(),
      child: Column(
        children: [
          FocusTraversalOrder(
            order: NumericFocusOrder(class="code-number">1),
            child: TextField(decoration: InputDecoration(labelText: class="code-string">'E-posta')),
          ),
          FocusTraversalOrder(
            order: NumericFocusOrder(class="code-number">2),
            child: TextField(decoration: InputDecoration(labelText: class="code-string">'Şifre')),
          ),
          FocusTraversalOrder(
            order: NumericFocusOrder(class="code-number">3),
            child: ElevatedButton(onPressed: _login, child: Text(class="code-string">'Giriş Yap')),
          ),
        ],
      ),
    )

    Modal'larda Odak Kapanı

    Bir diyalog veya alt sayfa açıldığında, odak içeride tutulmalıdır; böylece kullanıcı yanlışlıkla arka plan içeriğiyle etkileşime giremez. Flutter'ın `showDialog` ve `showModalBottomSheet` fonksiyonları bunu yönetir, ancak özel overlay'ler yönetmeyebilir. Her zaman doğrulayın.

    Dokunma Hedefi Boyutu

    WCAG 2.5.5, minimum dokunma hedefi olarak 44 x 44 CSS pikseli önerir. Material Design kılavuzları 48 x 48 dp önerir. Küçük dokunma hedefleri, motor beceri kısıtlılığı olan kullanıcılar için büyük bir engeldir.

    dart
    class=class="code-string">"code-comment">// Minimum dokunma hedefi boyutunu sağlayın
    SizedBox(
      width: class="code-number">48,
      height: class="code-number">48,
      child: IconButton(
        icon: Icon(Icons.close),
        onPressed: _dismiss,
      ),
    )

    `IconButton` varsayılan olarak 48 dp kısıtlamasını uygular, ancak `GestureDetector` tabanlı özel widget'lar genellikle bunu yapmaz.

    Sık Yapılan Erişilebilirlik Hataları

    Onlarca Flutter uygulamasının denetimini yaptıktan sonra, en sık karşılaştığım sorunları paylaşmak istiyorum:

  • **Dekoratif görsellerde ExcludeSemantics eksikliği.** Her `Image` widget'ı, siz açıkça hariç tutmadığınız sürece ekran okuyucu tarafından algılanır. Arka plan görselleri, ayırıcılar, marka filigranları — hepsini gizleyin.
  • **Etiketsiz ikon düğmeleri.** `tooltip` olmayan bir `IconButton`, yalnızca "düğme" olarak duyurulur. Her zaman `tooltip` belirleyin — Flutter bunu otomatik olarak semantik etiket olarak kullanır.
  • **Öğeleri gizlemek için Opacity(opacity: 0) kullanımı.** Widget semantik ağaçta kalmaya devam eder. Bunun yerine widget'ı ağaçtan tamamen kaldıran `Visibility(visible: false)` veya `Offstage` kullanın.
  • **Metin ölçeklendirmeyi görmezden gelmek.** Geliştiriciler yalnızca 1.0x'te test eder. 2.0x ölçeklendirme kullanan bir kullanıcı uygulamayı açtığında metinler taşar, düzenler bozulur ve kritik aksiyonlara erişilemez.
  • **Özel widget'larda tamamen eksik semantikler.** Elle yapılmış bir toggle, çizilmiş bir grafik, hareketle kontrol edilen bir kart — `Semantics` eklemediğiniz sürece bunlar yardımcı teknolojiler için görünmezdir.
  • **Hata duyurusu olmayan formlar.** Doğrulama başarısız olduğunda hata metni görsel olarak belirir ama asla duyurulmaz. `SemanticsService.announce` ile görsel hata göstergelerini birleştirin.
  • **Etiketsiz metin alanları.** Yalnızca `hintText` ile oluşturulan `TextField`, kullanıcı yazmaya başladığında etiketini kaybeder. Her zaman `InputDecoration(labelText: ...)` kullanın.
  • **Devre dışı bırakılamayan animasyonlar.** Bazı kullanıcılar hareket tutarsızlığı yaşar. `MediaQuery.disableAnimations` değerine saygı gösterin ve azaltılmış hareket alternatifleri sunun.
  • Otomatik Test

    Flutter, testlerde bazı erişilebilirlik sorunlarını yakalamak için araçlar sunar:

    dart
    testWidgets(class="code-string">'ana ekran erişilebilirlik kılavuzlarını geçiyor', (tester) async {
      await tester.pumpWidget(MyApp());
    
      final handle = tester.ensureSemantics();
    
      await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
      await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
      await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
      await expectLater(tester, meetsGuideline(textContrastGuideline));
    
      handle.dispose();
    });

    Bu kontrolleri CI pipeline'ınıza entegre edin, böylece gerilemeler kullanıcılara ulaşmadan yakalanır.

    Erişilebilirlik Kontrol Listesi

    Her sürümden önce bu listeyi kullanın:

  • [ ] Tüm etkileşimli öğelerin semantik etiketleri var
  • [ ] Dekoratif görseller semantik ağaçtan hariç tutulmuş
  • [ ] Renk kontrastı WCAG AA'yı karşılıyor (metin için 4.5:1, büyük metin için 3:1)
  • [ ] Renk, anlamın tek göstergesi olarak kullanılmıyor
  • [ ] Arayüz 2.0x metin ölçek faktöründe düzgün çalışıyor
  • [ ] Ekran okuyucu gezinme sırası görsel düzene uyuyor
  • [ ] Modal ve diyaloglarda odak doğru şekilde tutuluyor
  • [ ] Dokunma hedefleri en az 48 x 48 dp boyutunda
  • [ ] Dinamik içerik değişiklikleri SemanticsService ile duyuruluyor
  • [ ] Form hataları yalnızca görsel değil, sesli olarak da duyuruluyor
  • [ ] Animasyonlar azaltılmış hareket ayarına saygı gösteriyor
  • [ ] Otomatik erişilebilirlik testleri CI'da geçiyor
  • Sonuç

    Erişilebilir uygulamalar, yalnızca bir alt küme için değil, tüm kullanıcılar için daha iyi deneyimler yaratır. Yukarıdaki uygulamaların birçoğu — net semantikler, tahmin edilebilir odak, ölçeklenebilir düzenler — kullanıcının becerisinden bağımsız olarak kodunuzu daha temiz, ürününüzü daha sağlam hale getirir. Erişilebilirlik bir kez gönderdiğiniz bir özellik değil, her commit'te sürdürdüğünüz bir disiplindir.

    İsterseniz kritik kullanıcı akışlarınız için erişilebilirlik denetimi gerçekleştirebilirim — uygulamanızı herkes için çalışır hale getirelim.

    İlgili Makaleler

    Flutter Projeniz mi Var?

    iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.

    İletişime Geç