ethos 0.2.0 copy "ethos: ^0.2.0" to clipboard
ethos: ^0.2.0 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 in five minutes.
  • โœ… Pluggable detector registry โ€” add or replace rules without touching the core engine.
  • โœ… CI/CD ready โ€” JSON, Markdown, and human-readable outputs; exits with code 1 on critical failures.

Installation #

As a CLI #

dart pub global activate ethos
ethos -p ./my_flutter_app

As a library #

dependencies:
  ethos: ^0.2.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

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 (CircleIconBtn, AppButton, whatever your team calls them). Ethos can't guess those names, so without config they appear as indeterminate. To fix that, drop an ethos.yaml next to your pubspec.yaml:

# ethos.yaml โ€” OPTIONAL. Ethos auto-detects it, no flag needed.

widget_aliases:
  # Key = the widget's class name exactly as written in your 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

# Optional: tighten a threshold without rewriting the whole spec.
# rule_overrides:
#   wcag_1_4_3_contrast_minimum:
#     critical_threshold: 95

What each field teaches:

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.

No ethos.yaml? Ethos still runs on the built-in spec. Custom widgets are simply ignored (shown as indeterminate). For a vanilla Flutter project that's already useful. For a project with a design system, the aliases make all the difference.


Supported rules #

Five built-in rules, all backed by a RecursiveAstVisitor on real Dart AST.

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.
// โœ… PASS
Semantics(
  label: 'Open profile',
  child: GestureDetector(onTap: () {}, child: Icon(Icons.person)),
)

// โœ… PASS โ€” descendant Semantics 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) using the real WCAG luminance formula.

  • In scope: Text with inline TextStyle(color:, backgroundColor:) where both are literal (Color(0x...) or Colors.*).
  • Indeterminate: colors from Theme.of(context), custom style variables, or a missing inline background (the common case in well-structured Flutter code).
// โœ… 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 โ€” color from theme, cannot verify statically
Text('Hello', style: theme.textTheme.bodyLarge)

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 (Flutter guarantees 48ร—48); aliases with size_guaranteed: true.
  • Verifiable: custom interactive widget inside a SizedBox or Container with literal width/height โ€” pass if both โ‰ฅ 48, fail otherwise.
  • Indeterminate: size from a variable, intrinsic content, or tapTargetSize: shrinkWrap.

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 (ElevatedButton, TextField, InkWell, etc.); GestureDetector under a Focus, FocusScope, Shortcuts, or KeyboardListener ancestor; aliases with keyboard_ready: true.
  • Fail: GestureDetector.onTap with no keyboard path in its ancestor chain.

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 any layout with 2+ focusable inputs (TextField, Checkbox, Radio, etc.).
  • 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
  -v, --verbose        Show progress details (written to stderr)
  -h, --help           Show this help

Examples:
  ethos -p ./my_app
  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
  ethos -p ./my_app -v

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');

// With explicit config file
final analyzer = await CoverageAnalyzer.forProject(
  './my_app',
  configPath: 'path/to/ethos.yaml',
);

// Run analysis
final report = await analyzer.analyze();

// Output options
print(report.overallCoverage);          // double 0โ€“100
print(report.complianceLevel);          // 'A' | 'AA' | 'AAA' | 'NONE'
print(report.toJsonString());           // JSON for CI pipelines

Advanced: CoverageAnalyzer.loadFromFile(specPath, projectPath: ...) and .fromString(yaml, projectPath: ...) load a fully custom spec instead of the built-in.


Architecture #

ethos/
โ”œโ”€โ”€ bin/
โ”‚   โ””โ”€โ”€ analyze.dart              # CLI entry point
โ”œโ”€โ”€ lib/
โ”‚   โ”œโ”€โ”€ ethos.dart                # Public barrel export
โ”‚   โ””โ”€โ”€ src/
โ”‚       โ”œโ”€โ”€ models/
โ”‚       โ”‚   โ”œโ”€โ”€ spec.dart         # Spec, Rule, WidgetAlias, WidgetRole
โ”‚       โ”‚   โ”œโ”€โ”€ ethos_config.dart # User ethos.yaml model + 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 # Engine (forProject / analyze)
โ”‚           โ”œโ”€โ”€ spec_loader.dart       # Built-in + ethos.yaml merge
โ”‚           โ”œโ”€โ”€ detector_registry.dart
โ”‚           โ”œโ”€โ”€ rule_detector.dart     # RuleDetector interface
โ”‚           โ”œโ”€โ”€ ast/widget_visitor.dart
โ”‚           โ”œโ”€โ”€ utils/color_resolver.dart
โ”‚           โ””โ”€โ”€ detectors/
โ”‚               โ”œโ”€โ”€ semantic_labels_detector.dart
โ”‚               โ”œโ”€โ”€ contrast_detector.dart
โ”‚               โ”œโ”€โ”€ touch_target_detector.dart
โ”‚               โ”œโ”€โ”€ keyboard_detector.dart
โ”‚               โ””โ”€โ”€ focus_order_detector.dart
โ”œโ”€โ”€ example/
โ”‚   โ”œโ”€โ”€ main.dart
โ”‚   โ””โ”€โ”€ fixtures/
โ”‚       โ”œโ”€โ”€ ethos.yaml            # Sample widget aliases
โ”‚       โ””โ”€โ”€ lib/                  # Sample Dart files (analysis input)
โ”œโ”€โ”€ test/
โ”‚   โ”œโ”€โ”€ spec_compliance_test.dart
โ”‚   โ”œโ”€โ”€ semantic_labels_detector_test.dart
โ”‚   โ”œโ”€โ”€ contrast_detector_test.dart
โ”‚   โ”œโ”€โ”€ touch_target_detector_test.dart
โ”‚   โ”œโ”€โ”€ keyboard_detector_test.dart
โ”‚   โ””โ”€โ”€ focus_order_detector_test.dart
โ””โ”€โ”€ tool/
    โ””โ”€โ”€ embed_spec.dart           # Regenerates wcag_2_2_embedded.dart

If you edit the built-in spec (lib/src/specs/v1/wcag_2_2.yaml), regenerate the embedded constant:

dart run tool/embed_spec.dart

Roadmap #

v0.3.0 #

  • Widget alias inheritance โ€” alias a widget once and have child widgets inherit its traits.
  • Cross-method/cross-file resolution so a Semantics wrapper in a parent widget is connected 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