ethos 0.8.3
ethos: ^0.8.3 copied to clipboard
Measure accessibility coverage in Flutter apps using WCAG 2.2 specifications with Spec-Driven Development.
Ethos #
Measure accessibility coverage in Flutter apps using WCAG 2.2 specifications with Spec-Driven Development.
What is Ethos? #
Ethos measures what percentage of your Flutter widgets comply with WCAG 2.2 accessibility standards.
Unlike tools that detect individual issues, Ethos calculates coverage metrics for each rule, giving you a clear picture of your app's overall accessibility maturity.
๐ Overall Coverage: 44.6%
Compliance Level: NONE
๐ Coverage by Rule:
โ
Semantic Labels: 96% (26/27)
โน๏ธ Color Contrast: NO DATA โ 82 indeterminate
โน๏ธ Touch Targets: NO DATA โ 905 indeterminate
โ ๏ธ Keyboard Nav: 66% (8/12) โ CRITICAL
โ ๏ธ Focus Order: 0% (0/1) โ CRITICAL
โ ๏ธ Non-text Content: 15% (7/45) โ CRITICAL
โน๏ธ Resize Text: NO DATA (no hardcoded scale โ โ
correct)
Features #
- โ WCAG 2.2 alignment โ coverage metrics, not one-off issue lists.
- โ
Honest AST analysis with
package:analyzerโ no regex, no guessing. - โ Indeterminate accounting โ values from themes or runtime variables are reported separately and never inflate pass/fail ratios.
- โ Built-in spec, zero setup โ the WCAG 2.2 rules ship inside the package. You don't copy any YAML file.
- โ
Optional
ethos.yamlโ teach Ethos about your own design-system widgets and colors in five minutes. - โ
Theme-aware contrast โ resolves
theme.textTheme.Xautomatically from yourThemeData, and accepts explicitcolor_aliasesfor custom style variables. - โ
Deep analysis mode (
--deep) โ usesAnalysisContextCollectionto resolve cross-file references, class hierarchies, and type information. EmitsStream<AnalysisProgress>events for live progress. Falls back to standard mode automatically if the project is not ready. - โ Pluggable detector registry โ add or replace rules without touching the core engine.
- โ
Watch mode (
ethos watch) โ re-analyzes only the changed file on every save, prints the full report withโฒ/โผdeltas, and highlights new findings. - โ
Flutter test integration โ
package:ethos/ethos_test.dartprovidesexpect(report, meetsAccessibilityLevel(WcagLevel.a))matchers andEthosTestHelper.analyzeSource(dartCode)for inline detector unit tests.
Installation #
As a CLI #
dart pub global activate ethos
# Verify installation
ethos --version
# From inside your Flutter project:
cd ./my_flutter_app
ethos
As a library #
dependencies:
ethos: ^0.8.2
import 'package:ethos/ethos.dart';
void main() async {
final analyzer = await CoverageAnalyzer.forProject('./my_flutter_app');
final report = await analyzer.analyze();
print('Coverage: ${report.overallCoverage}%');
print('Compliance: ${report.complianceLevel}');
}
You do not copy any spec file. The built-in WCAG 2.2 spec lives inside the package.
Quick start (local development) #
git clone https://github.com/gearscrafter/ethos.git
cd ethos
dart pub get
# Run against the bundled fixtures
dart run example/main.dart
# Install locally as a global command
dart pub global activate --source path .
# Verify installation
ethos --version
# Analyze from inside a Flutter project โ no -p needed
cd ./my_flutter_app
ethos
ethos --deep -v
Standard vs Deep analysis #
Ethos has two analysis modes:
| Standard | Deep (--deep) |
|
|---|---|---|
| Speed | Fast (seconds) | Slower (10โ60s) |
| Cross-file resolution | โ | โ |
| Class hierarchy traversal | โ | โ |
| Variable type resolution | โ | โ |
| Progress stream | โ | โ |
Requires flutter pub get |
โ | โ (auto-detects) |
Standard mode is ideal for quick checks and CI gates. Deep mode is for comprehensive audits โ it finds widgets that standard mode misses because they are defined in a different file from where they are used.
# Standard (from inside the project)
ethos
# Deep โ with live progress
ethos --deep -v
Deep mode falls back to standard automatically if the project context cannot
be built (e.g. flutter pub get has not been run).
Deep mode as a library #
final deepAnalyzer = await DeepAnalyzer.forProject('./my_app');
await for (final event in deepAnalyzer.analyze()) {
switch (event) {
case AnalysisLoadingContext(:final totalFiles):
print('Loading $totalFiles files...');
case AnalysisAnalyzingFile(:final current, :final total):
print('[$current/$total]');
case AnalysisWarning(:final message):
print('โ ๏ธ $message');
case AnalysisComplete():
final report = (event as AnalysisComplete).report;
print(report.toJsonString());
default:
break;
}
}
Getting started with ethos init #
If your project uses a custom design system, the first thing to do after
installing Ethos is run ethos init. It scans your project for custom widgets
and unresolvable color expressions, then generates a starter ethos.yaml with
everything pre-filled โ you just review and fill in the values it can't infer.
# from inside your project
ethos init
Example output:
๐ Scanning ./my_app for custom widgets and color tokens...
Scanned 189 Dart files
Found 12 custom widget(s), 5 color expression(s)
โ
Generated: ./my_app/ethos.yaml
๐ฆ Custom widgets (fill in role and label_arg):
CircleIconBtn 47 uses
AppBtn 23 uses
WonderIllustration 18 uses
AppHeader 9 uses
๐จ Color expressions (add hex values to enable contrast checks):
$styles.text.body 31 uses
$styles.colors.offWhite 14 uses
Next steps:
1. Open ./my_app/ethos.yaml
2. Set role: for each widget_alias
3. Uncomment label_arg, size_guaranteed, keyboard_ready as needed
4. Fill in hex values under color_aliases
5. Run: ethos -v
The generated ethos.yaml looks like this โ nothing is invented, only
discovered. Values that require human knowledge are left as comments:
# ethos.yaml โ generated by `ethos init`
widget_aliases:
# CircleIconBtn โ used 47 times
CircleIconBtn:
role: button # button | text | input โ REQUIRED
# label_arg: ??? # e.g. semanticLabel, a11yLabel, label
# size_guaranteed: true
# keyboard_ready: true
# AppBtn โ used 23 times
AppBtn:
role: button
# label_arg: ???
color_aliases:
# used 31 times
# "$styles.text.body":
# foreground: "#REPLACE_ME"
# background: "#REPLACE_ME"
Once you fill in the values and re-run ethos -p ./my_app, the widgets that
were previously invisible (indeterminate) will start contributing to your
coverage score.
Live feedback with ethos watch #
ethos watch performs an initial full scan and then re-analyzes only the
file you just saved. Every change prints the full report with coverage deltas
so you can see the impact of each edit immediately.
# from inside your project
ethos watch
# With deep analysis on each change:
ethos watch --deep
Example output after saving a file:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ buttons.dart changed (14:23:05)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
Initial scan complete (189 files)
๐ Overall: 71.4% โฒ +2.1% ยท Compliance: A
โ
Semantic Labels 85.0% (17/20) โฒ +5.0%
โน๏ธ Color Contrast 100.0% (8/8)
โ
Touch Targets 100.0% (8/8)
โ ๏ธ Keyboard Nav 66.7% (8/12) โผ -3.3%
โ
Focus Order 95.0% (19/20)
โ ๏ธ Non-text Content 15.6% (7/45) CRITICAL
โน๏ธ Resize Text NO DATA
๐ Findings in buttons.dart:
โ line 42 โ GestureDetector: Tap gesture has no keyboard alternative
Watching for changes... (Ctrl+C to stop)
Watch mode observes lib/, test/, and example/ with a 300 ms debounce.
Generated files (.g.dart, .freezed.dart) are ignored automatically.
Watch mode as a library #
final engine = await WatchEngine.forProject('./my_app');
// Initial full scan
final baseline = await engine.initialScan(
onProgress: (current, total, path) {
print('[$current/$total] $path');
},
);
// Re-analyze on file change
final (newReport, diff) = await engine.reanalyzeFile(changedPath);
print('Overall delta: ${diff.overallDelta > 0 ? "โฒ" : "โผ"} ${diff.overallDelta.abs().toStringAsFixed(1)}%');
print('New critical rules: ${diff.newCritical}');
print('Resolved rules: ${diff.resolvedCritical}');
Testing your Flutter project's accessibility #
Ethos ships a separate test utilities library so you can write accessibility
assertions directly inside your existing dart test or flutter_test suites.
Setup #
Add Ethos as a dev dependency:
# pubspec.yaml
dev_dependencies:
ethos: ^0.8.2
test: ^1.24.0 # or flutter_test if you're in a Flutter project
Project-level accessibility test #
Analyze your whole lib/ folder once in setUpAll and assert against the
report. This is the recommended pattern for CI gates:
// test/accessibility_test.dart
import 'package:test/test.dart';
import 'package:ethos/ethos_test.dart';
void main() {
group('Accessibility coverage', () {
late CoverageReport report;
setUpAll(() async {
report = await EthosTestHelper.analyzeProject('lib/');
});
test('meets WCAG Level A', () {
expect(report, meetsAccessibilityLevel(WcagLevel.a));
});
test('semantic labels above 80%', () {
expect(report, hasRuleCoverage(
'wcag_1_3_1_semantics_label',
greaterThan(80),
));
});
test('no critical failures', () {
expect(report, hasNoCriticalFailures());
});
test('no image accessibility issues', () {
expect(report, hasNoFindingsFor('wcag_1_1_1_non_text_content'));
});
});
}
Run it with:
dart test test/accessibility_test.dart
# or
flutter test test/accessibility_test.dart
Exit code 1 when any assertion fails โ plugs straight into CI.
Detector unit tests with inline source #
EthosTestHelper.analyzeSource parses a Dart snippet in memory without
touching the filesystem. Use it to test specific widgets in isolation:
import 'package:test/test.dart';
import 'package:ethos/ethos_test.dart';
void main() {
group('Icon accessibility', () {
test('Icon with semanticLabel passes', () async {
final report = await EthosTestHelper.analyzeSource(
"Icon(Icons.search, semanticLabel: 'Search artifacts')",
);
expect(report, passesRule('wcag_1_1_1_non_text_content'));
});
test('Icon without semanticLabel fails', () async {
final report = await EthosTestHelper.analyzeSource('Icon(Icons.close)');
final cov = report.coverage['wcag_1_1_1_non_text_content'];
expect(cov?.findings, isNotEmpty);
});
});
group('GestureDetector accessibility', () {
test('with Semantics ancestor passes', () async {
final report = await EthosTestHelper.analyzeSource('''
Semantics(
label: 'Open profile',
child: GestureDetector(
onTap: () => navigate(),
child: Icon(Icons.person),
),
)
''');
expect(report, passesRule('wcag_1_3_1_semantics_label'));
});
test('without Semantics fails', () async {
final report = await EthosTestHelper.analyzeSource('''
GestureDetector(onTap: () => navigate(), child: Icon(Icons.settings))
''');
final cov = report.coverage['wcag_1_3_1_semantics_label'];
expect(cov?.findings, isNotEmpty);
});
});
group('Color contrast', () {
test('black on white passes', () async {
final report = await EthosTestHelper.analyzeSource(
"Text('Hello', style: TextStyle(color: Colors.black, backgroundColor: Colors.white))",
);
expect(report, passesRule('wcag_1_4_3_contrast_minimum'));
});
test('light grey on white fails', () async {
final report = await EthosTestHelper.analyzeSource(
"Text('Hello', style: TextStyle(color: Color(0xFFCCCCCC), backgroundColor: Color(0xFFFFFFFF)))",
);
final cov = report.coverage['wcag_1_4_3_contrast_minimum'];
expect(cov?.findings, isNotEmpty);
});
});
}
Available matchers #
| Matcher | Description |
|---|---|
meetsAccessibilityLevel(WcagLevel.a) |
Overall coverage meets Level A (โฅ70%), AA (โฅ85%), or AAA (โฅ95%) |
passesRule('rule_id') |
A specific rule is not below its critical threshold |
hasRuleCoverage('rule_id', greaterThan(80)) |
Rule coverage satisfies any Matcher |
hasNoCriticalFailures() |
No rule is below its critical threshold |
hasNoFindingsFor('rule_id') |
No findings for a specific rule |
One-liner smoke test #
For a quick CI check with no setup:
test('no critical a11y failures', () async {
await EthosTestHelper.expectNoCriticalFailures('lib/');
});
Throws a descriptive TestFailure listing every critical rule if any exist.
Ethos works out of the box โ the built-in spec already covers Flutter's
standard widgets (GestureDetector, InkWell, IconButton, TextField,
etc.).
Most real apps wrap controls in their own design-system components and define
colors in a custom style object. Drop an ethos.yaml next to your
pubspec.yaml to teach Ethos about them:
# ethos.yaml โ OPTIONAL. Ethos auto-detects it; no flag needed.
widget_aliases:
# Key = your widget's class name exactly as written in code.
CircleIconBtn:
role: button # button | text | input
label_arg: semanticLabel # which arg carries the accessible label
size_guaranteed: true # already wraps a >= 48ร48 target internally?
keyboard_ready: true # keyboard-operable out of the box?
AppButton:
role: button
label_arg: a11yLabel
color_aliases:
# Teach Ethos about your design-system color expressions so the contrast
# rule can compute real WCAG ratios instead of reporting "indeterminate".
"$styles.text.body":
foreground: "#212121" # required โ the text color
background: "#FFFFFF" # optional โ the default background color
"$styles.colors.primary":
foreground: "#1565C0"
# Optional: tighten a threshold without rewriting the spec.
# rule_overrides:
# wcag_1_4_3_contrast_minimum:
# critical_threshold: 95
widget_aliases
| Field | Detector | Effect |
|---|---|---|
role: button |
Semantic Labels, Keyboard, Touch Target | Widget counts as an interactive control. |
label_arg |
Semantic Labels | Look for the semantic label in this argument. |
size_guaranteed |
Touch Target Size | Auto-PASS โ already โฅ 48ร48 internally. |
keyboard_ready |
Keyboard Accessibility | Auto-PASS โ keyboard-operable out of the box. |
color_aliases
Maps a design-system style expression to concrete hex colors. Both
#RRGGBB and #AARRGGBB formats are accepted. When background is
omitted, the element remains indeterminate.
No ethos.yaml? Ethos still runs on the built-in spec. For a vanilla Flutter
project that's already useful; for a project with a design system, the aliases
make all the difference โ and deep mode can discover many of them automatically.
Supported rules #
Seven built-in rules, all backed by RecursiveAstVisitor on real Dart AST.
Deep mode runs enhanced versions of rules 1 and 2.
1. Semantic Labels โ wcag_1_3_1_semantics_label (WCAG 1.3.1 ยท Level A) #
Custom interactive widgets must have an accessible label.
- In scope:
GestureDetector,InkWell,InkResponsewith tap-like gestures, plus anyrole: buttonalias fromethos.yaml. - Pass: wrapped in
Semantics(label: '<non-empty literal>')as ancestor or descendant; or the aliaslabel_argis a non-empty literal. - Indeterminate: label is a variable, interpolation, or runtime call.
- Excluded automatically:
excludeFromSemantics: true, drag/pan-only gestures,onTap: () {}(block-parent), tap-to-dismiss patterns. - Deep mode: also follows widget definitions across files and detects
cross-method
Semanticswrappers.
// โ
PASS โ Semantics as ancestor
Semantics(
label: 'Open profile',
child: GestureDetector(onTap: () {}, child: Icon(Icons.person)),
)
// โ
PASS โ Semantics as descendant also works
GestureDetector(
onTap: () => navigate(),
child: Semantics(label: 'Go to settings', child: Icon(Icons.settings)),
)
// โ FAIL
GestureDetector(onTap: () => navigate(), child: Icon(Icons.settings))
2. Minimum Color Contrast โ wcag_1_4_3_contrast_minimum (WCAG 1.4.3 ยท Level AA) #
Text must have at least 4.5:1 contrast (3:1 for large text โฅ 18 pt). Resolution is attempted in layers:
- Inline literals โ
TextStyle(color: Color(0xFF...), backgroundColor: ...). - ThemeData extraction โ resolves
theme.textTheme.bodyLargeetc. color_aliasesโ resolves design-system expressions fromethos.yaml.- Deep mode only โ follows variable references across files.
// โ
PASS โ ratio 21:1
Text('Hello', style: TextStyle(color: Colors.black, backgroundColor: Colors.white))
// โ FAIL โ ratio ~1.6:1
Text('Hello', style: TextStyle(color: Color(0xFFCCCCCC), backgroundColor: Colors.white))
// โ INDETERMINATE in standard mode; resolved in deep mode
Text('Hello', style: TextStyle(color: bodyColor)) // bodyColor defined elsewhere
3. Touch Target Size โ wcag_2_5_5_target_size_enhanced (WCAG 2.5.5 ยท Level AAA) #
Interactive elements must be at least 48ร48 logical pixels.
- Auto-pass:
IconButton,FloatingActionButton; aliases withsize_guaranteed: true. - Verifiable: custom widget in a
SizedBox/Containerwith literal dimensions. - Indeterminate: size from a variable or intrinsic content.
4. Keyboard Accessibility โ wcag_2_1_1_keyboard (WCAG 2.1.1 ยท Level A) #
All interactive functionality must be reachable by keyboard.
- Pass: Material controls;
GestureDetectorunderFocus/FocusScope/Shortcuts/KeyboardListener; aliases withkeyboard_ready: true. - Fail:
GestureDetector.onTapwith no keyboard path. - Excluded:
excludeFromSemantics: true(visual-only wrappers).
5. Focus Order โ wcag_2_4_3_focus_order (WCAG 2.4.3 ยท Level A) #
Multi-input layouts must declare explicit focus management.
- In scope:
Formwidgets, or layouts with 2+ focusable inputs. - Pass: declares
FocusNode,FocusScope,FocusTraversalGroup, orautofocus: true.
6. Non-text Content โ wcag_1_1_1_non_text_content (WCAG 1.1.1 ยท Level A) #
All images and icons that convey information must have a text alternative. Purely decorative content must be explicitly excluded from the semantic tree.
- In scope:
Image,Image.asset,Image.network,Image.file,SvgPicture.asset,SvgPicture.network,Icon. - Pass: wrapped in
Semantics(label: '...'), orexcludeFromSemantics: true(decorative); forIcon, a non-empty literalsemanticLabel:argument. - Indeterminate: label is a runtime variable.
- Fail: no label and no explicit decoration marker.
// โ
PASS โ informative image with label
Semantics(
label: 'Photo of the Colosseum',
child: Image.network(url),
)
// โ
PASS โ decorative image explicitly excluded
Image.asset('assets/bg.png', excludeFromSemantics: true)
// โ
PASS โ icon with semantic label
Icon(Icons.search, semanticLabel: 'Search artifacts')
// โ FAIL โ image without any accessibility annotation
Image.network(artifactUrl)
// โ FAIL โ icon without semanticLabel
Icon(Icons.close)
7. Resize Text โ wcag_1_4_4_resize_text (WCAG 1.4.4 ยท Level AA) #
Text must be resizable up to 200% without loss of content. Hardcoding
textScaleFactor or textScaler to a fixed value ignores system font-size
preferences set by users with visual impairments.
- In scope: only
TextandMediaQuerywidgets that explicitly settextScaleFactorortextScaler. Widgets without these args are correct by default and are not counted. - Pass:
textScaleFactor: null(inherits system), or variable-based scaling that cannot be verified statically. - Fail: literal
textScaleFactor,TextScaler.noScaling, orTextScaler.linear(<literal>).
// โ
PASS โ inherits system preference (the right default)
Text('Hello')
// โ
PASS โ explicitly null (same as default)
Text('Hello', textScaleFactor: null)
// โ FAIL โ locks font size, ignores accessibility settings
Text('Hello', textScaleFactor: 1.0)
// โ FAIL โ forces no scaling
Text('Hello', textScaler: TextScaler.noScaling)
NO DATA is a good sign for Resize Text. It means your project does not override text scaling anywhere โ which is exactly what WCAG requires.
CLI reference #
-p is optional in all commands โ when omitted, Ethos uses the current
working directory. Run cd my_flutter_app first and then:
ethos # standard analysis of current directory
ethos --deep -v # deep mode with progress
ethos init # generate ethos.yaml here
ethos watch # watch for changes here
ethos watch --deep # watch + deep
Or pass -p explicitly from anywhere:
ethos -p <project-path> [options]
ethos init -p <project-path>
ethos watch -p <project-path>
Options (analyze):
-p, --project-path Path to the Flutter project (default: current directory)
-c, --config Path to a custom ethos.yaml (default: auto-detect)
-r, --report-type Output format: human | json | markdown | coverage
(default: human)
-o, --output Write report to this file instead of stdout
-d, --deep Deep analysis: resolves types and cross-file references.
Slower but more precise. Requires `flutter pub get`.
Falls back to standard mode if project is not ready.
-v, --verbose Show progress details (written to stderr)
-h, --help Show this help
Options (watch):
-p, --project-path Path to the Flutter project (default: current directory)
-d, --deep Use deep analysis on each change
-h, --help Show this help
More examples:
ethos -p ./my_app -r json -o report.json
ethos -p ./my_app -r markdown -o report.md
ethos init -p ./my_app -o path/to/ethos.yaml
Verbose logs go to stderr so ethos -r json | jq works cleanly.
Exit code 1 when any rule is below its critical threshold โ useful as a CI gate.
Compliance levels #
| Level | Minimum coverage | Description |
|---|---|---|
| AAA | โฅ 95% | Enhanced accessibility |
| AA | โฅ 85% | Strong accessibility (typical target) |
| A | โฅ 70% | Basic accessibility |
| NONE | < 70% | Does not meet minimum standards |
Library API reference #
// Standard entry point
final analyzer = await CoverageAnalyzer.forProject('./my_app');
final report = await analyzer.analyze();
// Deep entry point
final deepAnalyzer = await DeepAnalyzer.forProject('./my_app');
await for (final event in deepAnalyzer.analyze()) {
if (event is AnalysisComplete) {
final report = (event as AnalysisComplete).report;
print(report.toJsonString());
}
}
// Watch entry point
final engine = await WatchEngine.forProject('./my_app');
final baseline = await engine.initialScan();
final (newReport, diff) = await engine.reanalyzeFile(changedPath);
// Output
print(report.overallCoverage); // double 0โ100
print(report.complianceLevel); // 'A' | 'AA' | 'AAA' | 'NONE'
print(report.toJsonString()); // JSON for CI pipelines
Two separate import paths keep test utilities out of production builds:
import 'package:ethos/ethos.dart'; // main API โ use in production code
import 'package:ethos/ethos_test.dart'; // matchers + helpers โ use in tests only
Architecture #
ethos/
โโโ bin/
โ โโโ analyze.dart # CLI (analyze + init + watch subcommands)
โโโ lib/
โ โโโ ethos.dart # Public barrel export
โ โโโ src/
โ โโโ models/
โ โ โโโ spec.dart # Spec, Rule, WidgetAlias, WidgetRole
โ โ โโโ ethos_config.dart # EthosConfig, ColorAlias, RuleOverride
โ โ โโโ coverage_report.dart # CoverageReport, RuleCoverage, Finding
โ โโโ specs/v1/
โ โ โโโ wcag_2_2.yaml # Source spec โ edit this
โ โ โโโ wcag_2_2_embedded.dart # Generated constant โ do not edit
โ โโโ analyzer/
โ โโโ coverage_analyzer.dart # Standard engine
โ โโโ spec_loader.dart
โ โโโ detector_registry.dart
โ โโโ rule_detector.dart # RuleDetector interface
โ โโโ ast/widget_visitor.dart
โ โโโ utils/
โ โ โโโ color_resolver.dart
โ โ โโโ theme_extractor.dart
โ โโโ detectors/ # 7 standard detectors
โ โ โโโ semantic_labels_detector.dart
โ โ โโโ contrast_detector.dart
โ โ โโโ touch_target_detector.dart
โ โ โโโ keyboard_detector.dart
โ โ โโโ focus_order_detector.dart
โ โ โโโ non_text_content_detector.dart
โ โ โโโ resize_text_detector.dart
โ โโโ deep/
โ โ โโโ deep_analyzer.dart # Deep engine (Stream)
โ โ โโโ deep_detector.dart # DeepDetector interface
โ โ โโโ analysis_progress.dart # Sealed class: 6 event types
โ โ โโโ resolved_file.dart # ResolvedFile + ProjectIndex
โ โ โโโ detectors/
โ โ โโโ cross_file_semantic_labels_detector.dart
โ โ โโโ resolved_contrast_detector.dart
โ โโโ watch/
โ โ โโโ watch_engine.dart # Incremental cache + ReportDiff
โ โโโ init/
โ โโโ widget_discovery.dart # Scans for custom widgets/colors
โ โโโ ethos_yaml_generator.dart # Generates starter ethos.yaml
โโโ example/
โ โโโ main.dart
โ โโโ fixtures/
โ โโโ ethos.yaml
โ โโโ lib/
โโโ tool/
โโโ embed_spec.dart
Roadmap #
v1.0.0 #
- Animation preferences detector (
wcag_2_3_1) โ flagAnimationControllerand transitions that don't respectMediaQuery.disableAnimations. - Configurable rule subset โ run only the rules you care about.
- Stable public API with full WCAG 2.2 Level AA coverage.
Contributing #
Contributions are welcome. High-value areas:
- Additional WCAG 2.2 detectors.
- Improved theme/
$stylesresolution for the contrast rule. - CI/CD integration examples (GitHub Actions, GitLab CI).
License #
Apache-2.0 โ see LICENSE.
Author #
@gearscrafter โ Mobile Developer.
Resources #
- WCAG 2.2 Quick Reference
- Flutter Accessibility Docs
- Material Design 3 โ Accessibility
- WebAIM Contrast Checker
Made with โค๏ธ for inclusive Flutter apps.