Flutter

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

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

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: '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: '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: 4),
      Text('İ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(
    '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: 'Sipariş öğesi: Espresso',
  customSemanticsActions: {
    CustomSemanticsAction(label: 'Siparişten kaldır'): _removeItem,
    CustomSemanticsAction(label: 'Miktarı artır'): _increaseQty,
    CustomSemanticsAction(label: '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 gibi araçları kullanın. Flutter'da erişilebilirlik denetçi katmanını etkinleştirebilirsiniz:

dart
MaterialApp(
  showSemanticsDebugger: true,
  // ...
)

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: 8),
    Text(isOnline ? 'Çevrimiçi' : 'Ç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
// Kötü: ölçeklenmiş metni kırpacak sabit yükseklik
SizedBox(
  height: 48,
  child: Text('Ayarlar'),
)

// İyi: içeriğin büyümesine izin verin
Padding(
  padding: EdgeInsets.symmetric(vertical: 12),
  child: Text('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(1.0).clamp(1.0, 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

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

Android — TalkBack

  1. Ayarlar > Erişilebilirlik > TalkBack yolunu izleyip etkinleştirin.
  2. 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.
  3. Gezinmek için sağa kaydırın. Etkinleştirmek için çift dokunun.
  4. 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 'package:flutter/semantics.dart';

SemanticsService.announce('Ürün sepete eklendi. Sepet toplamı: 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(1),
        child: TextField(decoration: InputDecoration(labelText: 'E-posta')),
      ),
      FocusTraversalOrder(
        order: NumericFocusOrder(2),
        child: TextField(decoration: InputDecoration(labelText: 'Şifre')),
      ),
      FocusTraversalOrder(
        order: NumericFocusOrder(3),
        child: ElevatedButton(onPressed: _login, child: Text('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
// Minimum dokunma hedefi boyutunu sağlayın
SizedBox(
  width: 48,
  height: 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:

  1. 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.
  2. 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.
  3. Öğ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.
  4. 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.
  5. Ö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.
  6. 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.
  7. Etiketsiz metin alanları. Yalnızca hintText ile oluşturulan TextField, kullanıcı yazmaya başladığında etiketini kaybeder. Her zaman InputDecoration(labelText: ...) kullanın.
  8. 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('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ç