ethos 0.5.1 copy "ethos: ^0.5.1" to clipboard
ethos: ^0.5.1 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.

Pub License: Apache-2.0

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: 75.5%
โœ… Compliance Level: AA

๐Ÿ“‹ Coverage by Rule:
  โœ… Semantic Labels:  85% (17/20)
  โš ๏ธ  Color Contrast:  60% (6/10)  โ€” CRITICAL
  โœ… Touch Targets:   100% (12/12)
  โœ… Keyboard Nav:     90% (9/10)
  โœ… Focus Order:      95% (19/20)
     โ“˜ 6 indeterminate (color from theme โ€” not counted)

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.X automatically from your ThemeData, and accepts explicit color_aliases for custom style variables.
  • โœ… Deep analysis mode (--deep) โ€” uses AnalysisContextCollection to resolve cross-file references, class hierarchies, and type information. Emits Stream<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.

Installation #

As a CLI #

dart pub global activate ethos
ethos -p ./my_flutter_app

As a library #

dependencies:
  ethos: ^0.4.0
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
 
# Run against your own Flutter project
dart run bin/analyze.dart -p ./my_flutter_app
 
# Install locally as a global command
dart pub global activate --source path .
ethos -p ./my_flutter_app

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
ethos -p ./my_app
 
# Deep โ€” with live progress
ethos -p ./my_app --deep -v

Deep mode falls back to standard automatically if .dart_tool/package_config.json is missing.

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;
  }
}

Configuration (optional): ethos.yaml #

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 #

Five 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, InkResponse with tap-like gestures, plus any role: button alias from ethos.yaml.
  • Pass: wrapped in Semantics(label: '<non-empty literal>') as ancestor or descendant; or the alias label_arg is 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 Semantics wrappers.
// โœ… 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:

  1. Inline literals โ€” TextStyle(color: Color(0xFF...), backgroundColor: ...).
  2. ThemeData extraction โ€” resolves theme.textTheme.bodyLarge etc.
  3. color_aliases โ€” resolves design-system expressions from ethos.yaml.
  4. 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 with size_guaranteed: true.
  • Verifiable: custom widget in a SizedBox/Container with 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; GestureDetector under Focus/FocusScope/ Shortcuts/KeyboardListener; aliases with keyboard_ready: true.
  • Fail: GestureDetector.onTap with 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: Form widgets, or layouts with 2+ focusable inputs.
  • Pass: declares FocusNode, FocusScope, FocusTraversalGroup, or autofocus: true.

CLI reference #

ethos -p <project-path> [options]
 
Options:
  -p, --project-path   Path to the Flutter project to analyze (required)
  -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
 
Examples:
  ethos -p ./my_app
  ethos -p ./my_app --deep
  ethos -p ./my_app --deep -v
  ethos -p ./my_app -c path/to/ethos.yaml
  ethos -p ./my_app -r json -o report.json
  ethos -p ./my_app -r markdown -o report.md

Verbose logs go to stderr so ethos -p . -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());
  }
}
 
// Output
print(report.overallCoverage);   // double 0โ€“100
print(report.complianceLevel);   // 'A' | 'AA' | 'AAA' | 'NONE'
print(report.toJsonString());    // JSON for CI pipelines

Architecture #

ethos/
โ”œโ”€โ”€ bin/
โ”‚   โ””โ”€โ”€ analyze.dart               # CLI entry point (--deep flag)
โ”œโ”€โ”€ 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/              # 5 standard detectors
โ”‚           โ””โ”€โ”€ 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
โ”œโ”€โ”€ example/
โ”‚   โ”œโ”€โ”€ main.dart
โ”‚   โ””โ”€โ”€ fixtures/
โ”‚       โ”œโ”€โ”€ ethos.yaml
โ”‚       โ””โ”€โ”€ lib/
โ””โ”€โ”€ tool/
    โ””โ”€โ”€ embed_spec.dart

Roadmap #

v1.0.0 #

  • Widget alias inheritance โ€” alias a widget once and child widgets inherit its traits automatically.
  • Cross-method/cross-file resolution so a Semantics wrapper in a parent widget connects to a custom button in a child.
  • More built-in detectors: text scaling, alternative text on images, animation preferences.
  • Configurable rule subset โ€” run only the rules you care about.

Contributing #

Contributions are welcome. High-value areas:

  • Additional WCAG 2.2 detectors.
  • Improved theme/$styles resolution for the contrast rule.
  • CI/CD integration examples (GitHub Actions, GitLab CI).

License #

Apache-2.0 โ€” see LICENSE.

Author #

@gearscrafter โ€” Mobile Developer.

Resources #


Made with โค๏ธ for inclusive Flutter apps.

2
likes
0
points
197
downloads

Publisher

verified publishergearscrafter.dev

Weekly Downloads

Measure accessibility coverage in Flutter apps using WCAG 2.2 specifications with Spec-Driven Development.

Repository (GitHub)
View/report issues

Topics

#accessibility #wcag #flutter #testing #cli

License

unknown (license)

Dependencies

analyzer, args, collection, path, yaml

More

Packages that depend on ethos