a11yship 0.1.0
a11yship: ^0.1.0 copied to clipboard
Accessibility checks for Flutter apps (WCAG 2.2, EN 301 549, European Accessibility Act). Finds unlabeled buttons, low contrast, small touch targets and 200% text-size overflows in your tests and poin [...]
example/lib/main.dart
import 'package:flutter/material.dart';
void main() => runApp(const ShopApp());
class ShopApp extends StatelessWidget {
const ShopApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo Shop',
theme: ThemeData(colorSchemeSeed: Colors.teal),
home: const ProductListPage(),
);
}
}
class Product {
const Product(this.name, this.price);
final String name;
final double price;
}
const products = [
Product('Trail running shoes', 89.0),
Product('Rain jacket', 120.0),
Product('Merino socks', 15.5),
];
class ProductListPage extends StatelessWidget {
const ProductListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Shop'),
actions: [
// Issue: icon-only button without tooltip.
IconButton(icon: const Icon(Icons.shopping_cart), onPressed: () {}),
],
),
body: ListView(
children: [
// Issue: light grey text on white.
const Padding(
padding: EdgeInsets.all(16),
child: Text('Free returns within 30 days', style: TextStyle(color: Color(0xFFAAAAAA))),
),
for (final p in products)
ListTile(
title: Text(p.name),
subtitle: Text('${p.price.toStringAsFixed(2)} EUR'),
trailing: IconButton(
icon: const Icon(Icons.favorite_border),
tooltip: 'Save',
onPressed: () {},
),
onTap: () => Navigator.of(context).push(
MaterialPageRoute<void>(builder: (_) => ProductPage(product: p)),
),
),
],
),
);
}
}
class ProductPage extends StatelessWidget {
const ProductPage({super.key, required this.product});
final Product product;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(product.name)),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Issue: fixed height row overflows with large text.
SizedBox(
height: 24,
child: Row(
children: [
const Icon(Icons.local_shipping, size: 18),
const SizedBox(width: 4),
Text('Ships tomorrow with free tracking', style: Theme.of(context).textTheme.bodyMedium),
const Spacer(),
// Issue: tiny tap targets next to each other.
GestureDetector(onTap: () {}, child: const Icon(Icons.remove, size: 16)),
GestureDetector(onTap: () {}, child: const Icon(Icons.add, size: 16)),
],
),
),
const SizedBox(height: 16),
// Issue: placeholder-only label.
const TextField(decoration: InputDecoration(hintText: 'Coupon code')),
const SizedBox(height: 16),
FilledButton(onPressed: () {}, child: const Text('Add to cart')),
],
),
),
);
}
}