a11yship

Accessibility checks for Flutter apps that point to the line of code to fix.

a11yship runs inside your widget or integration tests, reads the same semantics tree TalkBack and VoiceOver use, and checks each captured screen against WCAG 2.2:

Rule WCAG Severity
Interactive element has no accessible name 4.1.2, 1.1.1 critical
Image has no text alternative 1.1.1 serious
Text field has no label 1.3.1, 3.3.2, 4.1.2 serious
Text contrast is too low (measured on rendered pixels) 1.4.3 serious
Touch target smaller than 24 x 24 (with spacing exception) 2.5.8 serious
Layout breaks at 200% text size 1.4.4 serious
Accessible name is not descriptive (ic_back.png, "button") 2.4.6 moderate
Screen has no heading 1.3.1, 2.4.6 moderate
Below 48dp / 44pt, duplicate names, placeholder-only labels best practice minor

Every finding includes where the widget was created in your code:

CRITICAL Interactive element has no accessible name (1)  WCAG 4.1.2, 1.1.1
  • [Product list] This button has no accessible name.  lib/main.dart:40
SERIOUS Text contrast is too low (1)  WCAG 1.4.3
  • [Product list] Contrast 2.21:1 for "Free returns within 30 days" (needs 4.5:1).  lib/main.dart:48

Install

flutter pub add dev:a11yship

Use

Create test/a11y/app_a11y_test.dart:

import 'package:a11yship/a11yship.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';

final scan = A11yShip(app: 'My App', guide: TargetGuide.android);

void main() {
  setUpAll(A11yShip.loadFonts); // real fonts in screenshots and measurements
  tearDownAll(scan.writeReport);

  testWidgets('home', (tester) async {
    tester.view.physicalSize = const Size(1170, 2532); // a phone
    tester.view.devicePixelRatio = 3;
    addTearDown(tester.view.reset);

    await tester.pumpWidget(const MyApp());
    await tester.pumpAndSettle();
    await scan.capture(tester, 'Home');
  });
}

Run it:

dart run a11yship scan                    # runs test/a11y, prints a summary
dart run a11yship scan --fail-on serious  # fail CI on serious or critical issues

Gate a single test instead: scan.expectNoIssues(severity: Severity.serious);

The report is written to build/a11yship/report.json with a PNG per screen.

Dashboard (optional)

Upload results to A11yShip to track every release, see issues on screenshots, run the manual WCAG checklist and export a PDF audit report and a European Accessibility Act statement:

A11YSHIP_TOKEN=... A11YSHIP_ENDPOINT=... dart run a11yship scan --upload

Limits

Automated checks find many common barriers but not all of them. Focus order, gestures, timing and screen reader announcements still need manual testing. A good score is not a certification of WCAG or EAA compliance.

Libraries

a11yship
Accessibility scanner for Flutter apps: finds WCAG 2.2 / EAA issues in widget and integration tests and points to the line of code to fix.
core
Pure-Dart part of a11yship (report model, rule catalog, rules engine). Safe to import from the Dart VM (CLI) and from Flutter web.