SKILL.md
$27
Task Progress: Platform-Specific Scanning
Copy this checklist to track your manual auditing progress across target platforms:
- If testing on Android:
- Install the Accessibility Scanner from Google Play.
- Enable it via Settings > Accessibility > Accessibility Scanner > On.
- Tap the Accessibility Scanner checkmark icon over your running app to initiate the scan.
- If testing on iOS:
- Open the
iosfolder in Xcode and run the app on a Simulator.
- Navigate to Xcode > Open Developer Tools > Accessibility Inspector.
- Select Inspection > Enable Point to Inspect and click UI elements to verify attributes.
- Select Audit > Run Audit to generate an issue report.
- If testing on Web:
- Open Chrome DevTools.
- Inspect the HTML tree under the
semantics hostnode.
- Navigate to the Elements tab and open the Accessibility sub-tab to inspect exported ARIA data.
- Visualize semantic nodes by running the app with:
flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true.
Task Progress: Automated Testing
Integrate Flutter's Accessibility Guideline API into your widget tests to catch contrast, target size, and labeling issues automatically.
- Create a dedicated test file (e.g.,
test/a11y_test.dart).
- Initialize the semantics handle using
tester.ensureSemantics().
- Assert against
androidTapTargetGuideline(48x48px minimum).
- Assert against
iOSTapTargetGuideline(44x44px minimum).
- Assert against
labeledTapTargetGuideline.
- Assert against
textContrastGuideline(3:1 minimum for large text).
- Dispose of the semantics handle at the end of the test.
Debugging the Semantics Tree
When semantic nodes are incorrectly placed or missing, execute the following feedback loop to identify and resolve the discrepancies.
- Run validator: Trigger a dump of the Semantics tree to the console.
- Enable accessibility via a system tool or
SemanticsDebugger.
- Invoke
debugDumpSemanticsTree()(e.g., bind it to aGestureDetector'sonTapcallback for easy triggering during debugging).
- Review errors: Analyze the console output to locate missing labels, incorrect roles, or improperly nested semantic nodes.
- Fix: Wrap the offending widgets in
SemanticsorMergeSemanticswidgets, apply the correctSemanticsRole, and repeat step 1 until the tree accurately reflects the visual UI.
Examples
Programmatically Enabling Web Accessibility
Force the Semantics tree to build immediately on Flutter Web.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
void main() {
runApp(const MyApp());
if (kIsWeb) {
SemanticsBinding.instance.ensureSemantics();
}
}
Explicitly Defining Semantic Roles
Assign explicit list and list-item roles to a custom layout.
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
class MyCustomListWidget extends StatelessWidget {
const MyCustomListWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Semantics(
role: SemanticsRole.list,
explicitChildNodes: true,
child: Column(
children: <Widget>[
Semantics(
role: SemanticsRole.listItem,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('Content of the first custom list item.'),
),
),
Semantics(
role: SemanticsRole.listItem,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('Content of the second custom list item.'),
),
),
],
),
);
}
}
Automated Accessibility Testing
Implement the Accessibility Guideline API in a widget test.
import 'package:flutter_test/flutter_test.dart';
import 'package:your_accessible_app/main.dart';
void main() {
testWidgets('Follows a11y guidelines', (tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(const AccessibleApp());
// Check tap target sizes
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
// Check labels and contrast
await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});
}