Kenapa A11y Penting
Fondasi aksesibilitas MASA
WCAG 2.1 AA DS v2.0 0/0 checked

Kenapa A11y Penting

Aksesibilitas bukan fitur tambahan — ini adalah syarat dasar untuk produk yang melayani umat Muhammadiyah dari semua kondisi dan usia. MASA harus bisa digunakan oleh semua orang.

MASA A11y Context: User base MASA sangat beragam — dari kader muda Gen Z hingga anggota senior usia 60+ tahun, dari kota besar hingga daerah dengan koneksi lambat. Banyak yang mungkin punya low vision, motor difficulty, atau hanya menggunakan HP entry-level. Aksesibilitas bukan edge case — ini mainstream audience MASA.
Legal Risk
Regulasi makin ketat
UU 8/2016 tentang Penyandang Disabilitas di Indonesia + WCAG menjadi standar acuan internasional. Aplikasi pemerintah dan layanan publik wajib accessible. MASA sebagai infrastruktur organisasi 100+ tahun harus comply.
Audience Reality
15% populasi punya disabilitas
WHO: 15% populasi global punya disabilitas. Di Indonesia: ~37 juta orang. Dalam target user MASA, ada lansia (low vision), anggota dengan motor difficulty, dan pengguna screen reader (tunanetra). Semua bisa jadi user MASA yang setia.
Good Design
A11y = UX yang lebih baik untuk semua
Curb cut effect: fitur yang dibuat untuk accessibility membantu semua user. Subtitel membantu user di tempat bising. High contrast membantu di bawah sinar matahari. Touch target besar membantu semua orang saat terburu-buru.

Target compliance MASA

Target compliance
WCAG 2.1 Level AA
Minimum yang harus dipenuhi sebelum Wave 0 release. Level AA adalah standar legal internasional dan best practice industri. Level AAA adalah aspirasi jangka panjang untuk fitur ibadah.
4.5:1
min contrast normal text
3:1
min contrast large text
44px
min touch target
200%
text scale support

WCAG Levels & Legal

3 level WCAG dan mana yang berlaku untuk MASA. Target: AA untuk semua fitur, AAA untuk fitur ibadah inti.

Level A
Minimum absolut — wajib
Jika tidak memenuhi Level A, banyak user tidak bisa menggunakan app sama sekali. Non-negosiable. Contoh: keyboard navigable, tidak ada content yang flashes >3x/detik, form labels ada.
Level AA ★
Target MASA — standar legal & industri
Ini yang harus dicapai semua fitur MASA sebelum release. Mencakup: contrast ratio 4.5:1, resize text 200%, captions untuk video, error suggestions yang jelas. Ini yang dipakai di regulasi aksesibilitas mayoritas negara.
Level AAA
Aspirasi — untuk fitur ibadah
Lebih strict: contrast 7:1, sign language, audio description lengkap. Tidak semua fitur perlu AAA, tapi Al-Qur'an reader dan Jadwal Salat (fitur yang paling sering digunakan oleh semua segmen) harus aspire ke AAA.

4 Prinsip WCAG — POUR

P
Perceivable
Informasi bisa dilihat, didengar, atau dirasakan. Alt text, contrast, captions.
O
Operable
Semua fungsi bisa dioperasikan tanpa hanya mengandalkan touch. Touch targets, keyboard, timing.
U
Understandable
Informasi dan UI mudah dipahami. Error messages jelas, consistent navigation, predictable behavior.
R
Robust
Konten cukup robust untuk diinterpretasikan oleh berbagai assistive technology.

Color Contrast

Semua kombinasi warna teks dan background di MASA harus memenuhi rasio kontras minimum. Normal text: 4.5:1. Large text (≥18px bold atau ≥24px): 3:1. UI components & icons: 3:1.

Token combinations — MASA color pairs

Teks utama12px
14.7:1
✓ AAA
--ink-0 on --paper-0 · Primary text default
Body text12px
8.2:1
✓ AA
--ink-2 on --paper-0 · Body secondary
Caption text12px
4.6:1
✓ AA
--ink-3 on --paper-0 · Caption · borderline
Placeholder12px
2.9:1
✕ Fail for small text
--ink-4 · placeholder only · ≥18px W700 atau ≥24px OK
Button label12px
7.5:1
✓ AAA
white on --blue-700 · Primary button, nav active
Sedekah12px
4.7:1
✓ AA
white on --amber-600 · FAB, amber CTA · borderline AA
Terverifikasi12px
5.8:1
✓ AA
white on --green-500 · Success, verified badge
Error msg12px
7.1:1
✓ AAA
--err-700 on --err-100 · Error card text
Info text12px
5.8:1
✓ AA
--blue-700 on --blue-50 · Info card, NOW zone text
⚠ --ink-4 pada background terang: Rasio 2.9:1 GAGAL untuk normal text (≤18px regular atau ≤14px bold). Gunakan --ink-4 HANYA untuk placeholder, timestamp, atau teks yang tidak critical. Untuk caption penting, naikkan ke --ink-3.

Dark background combinations (e-KTAM, splash)

Nama member
19.1:1
✓ AAA
white on --ink-0 · e-KTAM, splash
Sub text
6.2:1
✓ AA
--blue-100 on --blue-700 · e-KTAM sub text
Tanggal expired
3.2:1
✓ AA (large only)
70% white on blue-700 · ≥18px bold atau ≥24px only

Touch Targets

Semua elemen yang bisa di-tap WAJIB memiliki touch area minimum 44×44px. Visual element boleh lebih kecil, tapi total tappable area harus ≥44×44px.

Touch target hierarchy

Icon button
44×44px
✓ PASS
Button MD
auto × 44px
Bayar
✓ PASS
FAB
56px visual · 64px touch
✓ PASS
Bottom nav item
48px touch area
✓ PASS
Small icon TANPA wrapper
❌ 16×16px touch
✕ FAIL

Komponen dan touch area minimumnya

KomponenVisual sizeTouch targetFlutter implementation
Icon button (AppBar)24px icon44×44pxIconButton default (48×48px)
Button MDauto × 44pxauto × 44pxminimumSize: Size(0, 44)
FAB Sedekah56px visual64×64px (GestureDetector)FloatingActionButton wraps with InkWell
Toggle switch44×24px44×44px (InkWell wrapper)SwitchListTile atau manual wrap
Checkbox18×18px44×44pxCheckboxListTile atau InkWell constraints
Bottom nav item22px icon + label48×48px (auto)NavigationBar auto-handles
Puck grid itemaspect-ratio 1:1min 64×64pxInkWell full puck + SizedBox(min:64)
Chip/Filter32px height44px height minimumOverride materialTapTargetSize: padded
Text link inlineteks heightWrap dengan InkWell + paddingGestureDetector + Padding(8px vertical)

Text & Typography A11y

Semua teks harus scalable, tidak boleh hardcode ukuran pixel, dan harus tetap readable saat user scale ke 200%.

Dynamic type support — teks tidak dipotong saat dibesarkan
Semua teks menggunakan sp (Flutter) bukan px yang fixed. Saat user set font size di Settings Android/iOS ke large, teks harus membesar tanpa overflow atau clipping.
P0Flutter: sp unit auto
AA
200% text resize tidak merusak layout
Test dengan Accessibility > Font size 200% di Android. Teks harus tetap readable — tidak terpotong, tidak overflow keluar screen. List item boleh wrap ke 2 baris.
P0Flutter: layout harus flexible (Expanded, Flexible, Wrap)
AA
Minimum font size 12px (16sp equivalent)
Tidak ada teks yang readable di bawah 12px. Overline dan caption minimum 11px. Jika perlu lebih kecil, tingkatkan weight atau spacing untuk kompensasi.
P1
AA
Line height minimum 1.5× font size untuk body text
WCAG 1.4.12: line spacing minimum 1.5× untuk body text. Di MASA kita gunakan LH 1.6 untuk Body L/M — sudah comply. Perlu diverifikasi saat implementasi.
P1
AA
Letter spacing tidak terlalu ketat untuk body text
Display dan H1 boleh -3% letter spacing. Body text jangan negatif — minimum 0%. Terlalu ketat menggangu dyslexia users.
P2
AA
Arabic text (Qur'an) : direction RTL + font yang tepat
Ayat Qur'an harus menggunakan font Arabic yang proper (Uthmanic Hafs / KFGQPC) bukan fallback system font. Direction: RTL. Text alignment: right. Jangan auto-bidirectional dari mixed content.
P0Flutter: Directionality.rtl + TextDirection.rtl
AA

Motion Sensitivity

Semua animasi WAJIB punya reduced-motion fallback. Sudah dibahas lengkap di Motion Tokens — ini adalah checklist verifikasi.

Cross-reference: Detail implementasi reduced motion ada di Motion & Animation Tokens → Reduced Motion. Section ini hanya checklist verifikasi sebelum release.
prefers-reduced-motion dihormati di setiap animasi
MediaQuery.of(context).disableAnimations dicheck sebelum setiap AnimationController. Bukan optional.
P0MotionHelper.isReducedMotion(context)
AA
Skeleton shimmer dimatikan di reduced-motion mode
Shimmer adalah animasi infinite loop — wajib dimatikan. Ganti dengan static gray placeholder tanpa animation.
P0
AA
FAB pulse animation dimatikan di reduced-motion
Pulse infinite animation (opacity loop) harus dimatikan. FAB tetap visible tapi static.
P1
AA
Page transition menjadi fade saat reduced-motion
Slide transitions (push/pop) → simple opacity fade. Duration: 50ms (instant-feel).
P1
AA
Tidak ada animasi yang berulang tanpa pause (>5 detik)
WCAG 2.2.2: animasi yang bergerak/scroll/blink lebih dari 5 detik harus bisa dipause atau dihentikan.
P0
AA

Focus Management

Focus management memastikan user dengan keyboard atau switch access bisa navigate app secara linear dan logical.

Modal dan Dialog: focus pindah ke modal saat dibuka
Saat Dialog atau BottomSheet dibuka, focus harus pindah otomatis ke elemen pertama di dalam modal (biasanya title atau primary action). Focus tidak boleh tetap di belakang modal.
P0FocusScope.of(context).requestFocus()
AA
Focus dikembalikan saat modal ditutup
Saat Dialog/BottomSheet ditutup, focus harus kembali ke elemen yang membuka modal tersebut. User tidak boleh "tersesat" di halaman.
P0_focusNode.requestFocus() saat dismiss
AA
Focus order mengikuti logical reading order
Saat user navigasi dengan TalkBack/switch, focus harus bergerak top → bottom, left → right. Tidak melompat-lompat.
P1Semantics(child: ..., sortKey: OrdinalSortKey(n))
AA
Form fields: focus pindah otomatis ke field berikutnya
Setelah user selesai di satu field (e.g. OTP digit 1), focus harus otomatis pindah ke field berikutnya. Di OTP: auto-advance setelah 1 karakter.
P1FocusNode + textInputAction: next
AA
Error state: focus pindah ke error message atau field yang error
Saat form disubmit dengan error, focus harus pindah ke field pertama yang error. Screen reader user harus tahu apa yang salah.
P0errorFocusNode.requestFocus()
AA
Decorative elements exclude dari focus traversal
Background images, decorative icons, dividers — harus di-exclude dari focus. User TalkBack tidak perlu "mengunjungi" elemen yang tidak informatif.
P1ExcludeSemantics() atau Semantics(excludeSemantics: true)
AA

Screen Reader

TalkBack (Android) dan VoiceOver (iOS). Semua elemen interaktif harus punya label yang bermakna dalam Bahasa Indonesia.

Label rules: Label harus deskriptif tapi singkat. "Tombol back" ✓ · "Kembali ke halaman sebelumnya" ✓ · "button_12" ✗ · "Image" ✗ · "Tap here" ✗

Label requirements per element type

ElementRequired labelContoh label yang benarFlutter
Icon buttonDeskripsi aksi"Kembali ke beranda" · "Buka pengaturan" · "Bagikan artikel"Tooltip(message: '...') atau semanticLabel
ImageAlt text deskriptif atau empty untuk decorative"Foto distribusi qurban di NTT" · "" untuk dekorasiImage(semanticLabel: '...')
FABAksi yang dilakukan"Buka sedekah hari ini"FloatingActionButton(tooltip: '...')
ToggleState + aksi"Notifikasi adzan, aktif, ketuk untuk matikan"SwitchListTile(title: Text('...'), value: ...)
Progress barValue + context"Target donasi, 65 persen tercapai"Semantics(label: '...', value: '65%')
Bottom navTab name + state"Beranda, dipilih" · "Ibadah, tab 2 dari 5"NavigationDestination(label: '...', tooltip: '...')
Slide to confirmInstruksi + aksi"Geser ke kanan untuk konfirmasi sedekah Rp 5.000"Semantics(label: '...', hint: '...')
e-KTAM cardSeluruh konten card"Kartu anggota Muhammad Iqbal, NBM 12345678, PCM Caturtunggal, aktif sampai Juni 2026"Semantics(label: '...', child: ...)
Donation cardNama program + progress"Beasiswa santri, 65 persen dari target Rp 75 juta"MergeSemantics(child: ...)
Error messageAlert role + pesan"Peringatan: nomor rekening belum bisa dibuat"Semantics(liveRegion: true, ...)
Semua icon button punya semanticLabel dalam Bahasa Indonesia
Tidak ada icon yang di-tap tanpa label. "Back", "Close", "Share" harus dalam BI: "Kembali", "Tutup", "Bagikan".
P0
AA
Semua gambar punya alt text atau ditandai decorative
Foto program donasi: alt text deskriptif. Background gradient: excludeSemantics. Tidak boleh ada Image tanpa semanticLabel.
P0
AA
Live region untuk error dan success messages
Saat error muncul setelah aksi (VA gagal, form error), screen reader harus otomatis membacakan pesan tersebut tanpa user harus pindah focus dulu.
P0Semantics(liveRegion: true)
AA
Grouped elements menggunakan MergeSemantics
Card yang punya beberapa elemen (icon + title + subtitle) harus di-merge jadi satu semantics group agar screen reader membaca semuanya sekaligus, bukan satu per satu.
P1MergeSemantics(child: ...)
AA
Page title diumumkan saat navigasi
Saat user push ke halaman baru, TalkBack harus mengumumkan nama halaman. Di Flutter, pastikan Scaffold memiliki semanticLabel atau AppBar title terdeteksi.
P1Scaffold(semanticLabel: 'Jadwal Salat')
AA

Gestures & Input

Semua aksi yang menggunakan gesture kompleks (swipe, multi-touch) harus punya alternatif single-tap.

Swipe gesture punya alternatif button
Slide to confirm (Sedekah) harus punya fallback: kalau user tidak bisa swipe, tombol "Konfirmasi" standard harus tersedia. Juga: swipe to dismiss card harus punya delete button.
P1
AA
Pull to refresh punya alternatif button refresh
Pull-to-refresh gesture harus bisa ditrigger juga via Refresh button di AppBar atau via TalkBack action.
P1
AA
Long-press tidak satu-satunya cara untuk aksi penting
Jika long-press membuka menu (bookmark ayat, etc), aksi yang sama harus tersedia via icon button atau dots menu.
P2
AA
Timeout/session tidak terlalu cepat
Jika ada session timeout (misal: OTP), user harus bisa extend waktu. Default: minimal 20 detik untuk OTP resend. WCAG 2.2.1.
P1
AA
Tidak ada aksi yang dijalankan saat onPointerDown saja
Aksi harus terjadi di onPointerUp (GestureDetector.onTap), bukan onPointerDown. User harus bisa "cancel" dengan menggeser jari keluar sebelum melepas.
P0
AA

Component A11y Map

Setiap komponen dengan requirement aksesibilitas spesifik. Tiga kolom: What screen reader reads · Required attributes · Priority.

Komponen
Screen reader announces
Required Flutter attributes
Priority
Button (Primary)
"[label], tombol" · state: disabled = "tidak tersedia"
ElevatedButton(onPressed: null/fn, child: Text('label')) · label deskriptif dari verb+object
Wajib
Icon Button
"[semanticLabel], tombol"
IconButton(icon:..., tooltip: 'Kembali') atau Semantics(label:'Kembali', button:true)
Wajib
Text Field
"[label], text field" · value · error msg jika ada
TextFormField(decoration: InputDecoration(labelText:'Email', errorText: err)) · label selalu ada
Wajib
Toggle Switch
"[label], diaktifkan/dimatikan, ketuk untuk [ubah]"
SwitchListTile(title: Text('Notifikasi Adzan'), value: _v, onChanged: fn)
Wajib
Bottom Navigation
"[tab name], tab [n] dari 5, [dipilih/tidak dipilih]"
NavigationDestination(label: 'Beranda', icon: ..., tooltip: '...')
Wajib
FAB Sedekah
"Sedekah hari ini, tombol"
FloatingActionButton(tooltip: 'Sedekah hari ini', child: Icon(...))
Wajib
Donation Card
"Beasiswa santri, LazisMu, 65 persen terkumpul, ketuk untuk detail"
MergeSemantics(child: InkWell(...)) + Semantics(label: combined text)
Penting
e-KTAM Card
"Kartu anggota [nama], NBM [nomor], aktif, ketuk untuk lihat QR"
Semantics(label: '...', onTap: fn) wrapping whole card
Penting
Verification Badge
"Terverifikasi" atau "Menunggu verifikasi"
Semantics(label: 'Terverifikasi oleh Muhammadiyah')
Penting
Empty State
"[title]. [description]" · CTA button terpisah
Semantics(label: title + description) · icon: ExcludeSemantics
Penting
Progress Bar
"[label], [value] persen"
Semantics(label: 'Target donasi', value: '65%', child: LinearProgressIndicator(...))
Penting
Skeleton Loader
"Sedang memuat" (satu kali announcement)
Semantics(label: 'Sedang memuat', liveRegion: true)
Direkomendasikan
Toast / Snackbar
Auto-announced saat muncul: "[pesan]"
ScaffoldMessenger.showSnackBar auto-announces · pastikan message jelas
Wajib
Al-Qur'an Reader
Arabic text + translasi + nomor ayat
Semantics(label: 'Ayat 23: [translasi Indonesia]') · Arabic text boleh dibaca jika font support
Penting

Forms & Inputs A11y

Setiap field punya label permanen (bukan hanya placeholder)
Placeholder menghilang saat user mengetik. Label harus tetap terlihat. Gunakan InputDecoration(labelText: '...') — label float ke atas saat field aktif.
P0
AA
Error message terhubung ke field yang error (associatedId)
Screen reader harus mengumumkan error saat field di-focus, bukan hanya ketika error text visible. Flutter: errorText di InputDecoration otomatis ter-associate.
P0InputDecoration(errorText: '...')
AA
Required fields ditandai dengan teks (bukan hanya bintang merah)
Asterisk * saja tidak cukup untuk screen reader. Tambahkan "(wajib)" dalam label atau hint teks.
P1
AA
OTP: setiap cell punya label posisi
"Digit 1 dari 6" untuk masing-masing OTP field. User harus tahu mereka sedang di field mana.
P1
AA
Dropdown/select punya role "combobox" yang benar
Flutter DropdownButtonFormField otomatis punya semantics yang benar. Custom dropdown harus dianotasi manual.
P1DropdownButtonFormField auto-handles
AA
Amount picker: nilai yang dipilih diumumkan
"Rp 5.000 dipilih" saat user tap amount chip. Screen reader user tahu pilihan aktif mereka.
P1Semantics(selected: true, ...)
AA

Navigation A11y

Back navigation konsisten dan predictable
Back button selalu ada di halaman non-root. Android back gesture harus berfungsi sama seperti tap Back button. Tidak ada halaman yang "trap" user.
P0
AA
Tab order logis untuk screen reader di semua halaman
Cek dengan TalkBack: swipe kanan untuk next element harus mengikuti urutan visual. Tidak melompat ke elemen yang jauh atau tersembunyi.
P1
AA
Bottom sheet dan dialog tidak "bocor" focus ke background
Saat BottomSheet terbuka, user TalkBack tidak boleh bisa fokus ke elemen di belakang sheet. Focus harus terkunci di dalam sheet.
P0ModalBarrier + FocusTrap sudah built-in di showModalBottomSheet
AA
Breadcrumb / lokasi saat ini jelas untuk screen reader
User harus tahu di mana mereka berada di app. AppBar title + screen name yang diumumkan saat navigate cukup.
P2
AA

★ Master Checklist

Daftar lengkap semua item aksesibilitas. Centang saat sudah diverifikasi. Progress tersimpan di browser.

Progress keseluruhan
0/0
Centang item di setiap section untuk tracking progress

P0 — Critical (stop release if missing)

Contrast ratio ≥4.5:1 untuk semua body text
P0
AA
Touch targets ≥44×44px untuk semua tappable elements
P0
AA
Semua icon button punya semanticLabel dalam BI
P0
AA
Semua form field punya label permanen
P0
AA
Error messages terhubung ke field yang error
P0
AA
prefers-reduced-motion dihormati di semua animasi
P0
AA
Tidak ada konten yang berkedip >3x/detik
P0
A
Focus tidak terperangkap di modal (focus trap yang benar)
P0
AA
Dynamic type: teks tidak terpotong saat font 200%
P0
AA
Aksi terjadi di onTap/onPointerUp — bukan onPointerDown
P0
A
Live region untuk error dan success messages
P0
AA
Arabic Qur'an: RTL direction + proper Arabic font
P0
AA

P1 — Important (merge-blocker)

Skeleton loader dinonaktifkan di reduced-motion
P1
AA
Focus kembali ke trigger saat modal ditutup
P1
AA
Tab order logis di semua halaman (TalkBack swipe test)
P1
AA
Grouped card elements: MergeSemantics
P1
AA
Semua gambar punya alt text atau decorative (excludeSemantics)
P1
AA
Page title diumumkan saat navigasi
P1
AA
Line height body text minimum 1.5×
P1
AA
OTP cells punya label posisi ("Digit 1 dari 6")
P1
AA
Swipe gesture punya alternatif button
P1
AA

P2 — Recommended (backlog OK)

Progress bar: semantic label + value
P2
AA
Amount picker: "dipilih" diumumkan saat tap
P2
AA
Long-press punya alternatif menu
P2
AA
Letter spacing body text tidak negatif
P2
AA
Decorative elements excluded dari semantics
P2
AA

Flutter A11y Code

Implementasi aksesibilitas di Flutter — siap copy-paste.

Semantic labels — patterns

// ── 1. Icon button dengan label ── IconButton( icon: Icon(Icons.arrow_back), tooltip: 'Kembali', // TalkBack akan baca ini onPressed: () => Navigator.pop(context), ) // ── 2. Image dengan alt text ── Image.network( url, semanticLabel: 'Foto distribusi qurban di NTT, Juni 2026', ) // Decorative image (no semantics): ExcludeSemantics( child: Image.asset('assets/background.png'), ) // ── 3. Card dengan merged semantics ── MergeSemantics( child: InkWell( onTap: () => navigateToDonationDetail(), child: Column(children: [ Text('Beasiswa Santri'), Text('LazisMu Nasional'), LinearProgressIndicator(value: 0.65), ]), ), ) // ── 4. Custom progress bar dengan label ── Semantics( label: 'Target donasi terkumpul', value: '65 persen dari Rp 75 juta', child: LinearProgressIndicator(value: 0.65), ) // ── 5. Live region untuk error/success ── Semantics( liveRegion: true, child: Text( errorMessage, style: TextStyle(color: MasaColors.error700), ), ) // ── 6. Toggle dengan state yang jelas ── SwitchListTile( title: Text('Notifikasi Adzan'), subtitle: Text(_enabled ? 'Aktif' : 'Tidak aktif'), value: _enabled, onChanged: (v) => setState(() => _enabled = v), ) // ── 7. FAB dengan tooltip ── FloatingActionButton( onPressed: _openSedekah, tooltip: 'Sedekah hari ini', backgroundColor: MasaColors.amber600, child: Icon(Icons.favorite, semanticLabel: 'Sedekah'), )

Focus management patterns

// ── Focus ke modal saat dibuka ── final FocusNode _modalFocus = FocusNode(); showModalBottomSheet( context: context, builder: (ctx) => Focus( focusNode: _modalFocus, autofocus: true, child: PaymentSheet(), ), ).then((_) { // Focus kembali ke trigger saat ditutup _triggerButtonFocus.requestFocus(); }); // ── Error focus saat form submit gagal ── void _handleSubmit() { if (!_formKey.currentState!.validate()) { // Pindah focus ke field error pertama Future.microtask(() => _emailFocus.requestFocus()); return; } // ... process } // ── Semantics sort key untuk custom order ── Semantics( sortKey: OrdinalSortKey(1.0), // dibaca pertama child: Text('Judul card'), ) Semantics( sortKey: OrdinalSortKey(2.0), // dibaca kedua child: Text('Sub text'), )

Reduced motion + dynamic text helper

// lib/core/a11y/masa_a11y.dart class MasaA11y { // Check reduced motion static bool isReducedMotion(BuildContext context) => MediaQuery.of(context).disableAnimations; // Check large text mode static bool isLargeText(BuildContext context) => MediaQuery.of(context).textScaleFactor > 1.3; // Announce to screen reader static void announce(BuildContext context, String message) { SemanticsService.announce(message, TextDirection.ltr); } // Safe animation duration static Duration animDuration(BuildContext context, Duration normal) => isReducedMotion(context) ? Duration(milliseconds: 50) : normal; }

Testing & Tools

Cara verifikasi aksesibilitas sebelum release. 5 metode testing yang wajib.

Testing checklist per sprint

Test methodApa yang dicekTool / caraKapan
TalkBack testLabel icons, navigasi urutan, form errors, modal focusAktifkan TalkBack di Android Settings → Accessibility. Navigasi seluruh app dengan swipe only.Setiap Wave sebelum release
Contrast checkSemua teks melewati 4.5:1Figma: Contrast plugin. Flutter: accessibility_checker package. Atau manual dengan contrast checker tool.Setiap halaman baru
Large font testLayout tidak pecah saat 200%Android: Settings → Display → Font size → Largest. Navigasi semua screen.Per sprint
Reduced motion testSemua animasi punya fallbackAndroid: Settings → Accessibility → Remove animations. Atau MediaQuery.disableAnimations: true di test.Per sprint
Flutter Semantics DebuggerSemantic tree sudah benarMaterialApp(showSemanticsDebugger: true) — visualize semantics overlay.Saat implement screen baru

Flutter accessibility testing

// widget_test.dart — test semantics testWidgets('Icon button has semantic label', (tester) async { await tester.pumpWidget(BackButton()); expect( find.bySemanticsLabel('Kembali'), findsOneWidget, ); }); testWidgets('Error state announces to screen reader', (tester) async { await tester.pumpWidget( MaterialApp(home: PaymentForm(forceError: true)), ); await tester.pump(); expect( find.bySemanticsLabel(contains('VA gagal')), findsOneWidget, ); }); // Run accessibility checker (pub: accessibility_checker) void main() { AccessibilityChecker.runChecks = true; runApp(MasaApp()); } // Akan output warning untuk contrast issues, touch targets, etc.
Definition of Done — Aksesibilitas:
✅ Semua P0 items dicentang di Master Checklist
✅ TalkBack test dilakukan dan tidak ada broken navigation
✅ Large font 200% test dilakukan dan layout tidak pecah
✅ Contrast ratio semua teks melewati 4.5:1
✅ Reduced motion test dilakukan dan semua animasi punya fallback
Index · Iconography · DS v2 Accessibility Checklist v1.0 · 15 Jun 2026