flutter_2yaml
Convert Flutter .dart widget files into compact YAML representations optimized for LLM/AI token consumption. Supports bidirectional conversion — Dart to YAML and YAML back to Dart.
Why?
When feeding Flutter code to AI models, full .dart files waste tokens on boilerplate — imports, @override, super.key, BuildContext context, const keywords, semicolons, brackets. flutter_2yaml strips all that and produces structured YAML using CSS-like shorthands, pipe syntax, and arrow notation.
Measured across the fixtures in this repo, that saves 40-65% of tokens — around 50% on average at the default standard level, more on simple screens and at --level minimal, less on property-dense ones.
Before (Dart — 45 lines)
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
bool _isLoading = true;
@override
void initState() {
super.initState();
Future.delayed(const Duration(seconds: 3), () {
Navigator.pushReplacementNamed(context, '/home');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/logo.png', width: 200, height: 200),
const SizedBox(height: 20),
if (_isLoading)
const CircularProgressIndicator(color: Colors.blue),
],
),
),
);
}
}
After (YAML — 14 lines, standard level)
page: SplashScreen
type: StatefulWidget
state: [_isLoading: bool = true]
lifecycle:
initState: [Future.delayed(3s)]
build:
Scaffold:
bg: white
body:
Center:
child:
Column(center):
children:
- Image.asset: assets/logo.png | 200x200
- SizedBox: { h: 20 }
- if _isLoading: CircularProgressIndicator: { color: blue }
Compact Format Features
Pipe Syntax |
Text: "Hello World" | 20 | bold | white # text | fontSize | fontWeight | color
Image.asset: logo.png | 200x200 # source | dimensions
Icon: search | onTap → goSearch() # icon | callback
Icon takes no callbacks of its own, so an icon with a callback is generated as
the widget that does: onPressed becomes an IconButton, any other callback a
GestureDetector wrapping the Icon.
CSS-Like Shorthands
| Shorthand | Flutter Equivalent |
|---|---|
bg |
backgroundColor / color (in BoxDecoration) |
br |
borderRadius: BorderRadius.circular(N) |
p |
padding: EdgeInsets.all(N) |
px, py |
EdgeInsets.symmetric(horizontal/vertical: N) |
h, w |
height, width |
full |
double.infinity |
t, l, r, b |
top, left, right, bottom (Positioned) |
gap, runGap |
spacing, runSpacing (Wrap) |
shadow |
boxShadow: [BoxShadow(...)] |
border |
Border.all(...) → {c: color, w: width} |
Arrow Callback Notation →
onTap → goSearch() # onTap: () => controller.goSearch()
onPressed → handleSubmit # onPressed: handleSubmit
Parenthetical Alignment
Row(spaceBetween) # Row(mainAxisAlignment: MainAxisAlignment.spaceBetween)
Column(start) # Column(crossAxisAlignment: CrossAxisAlignment.start)
Column(center) # Column(mainAxisAlignment: MainAxisAlignment.center)
Scaffold Named Children
Scaffold:
bg: white
appBar:
AppBar: { title: Text: "Home" | 20 | bold, bg: blue }
drawer:
Drawer: { ... }
body:
Center: { ... }
floatingActionButton:
FloatingActionButton: { onPressed → add(), child: Icon: add }
bottomNavigationBar:
BottomNavigationBar: { ... }
Auto-Detection
- Page vs Widget:
page:for Scaffold-containing widgets,widget:for components - State Management: Auto-detects GetX, Riverpod, Bloc, Provider, MobX from imports
- Color Shorthand:
Colors.blue→blue,Color(0xFF123456)→#123456 - Icon Shorthand:
Icons.menu→menu,Icons.search→search - Dimension Shorthand:
width: 80, height: 80→80x80,double.infinity→full - Theme Shorthand:
Theme.of(context).textTheme.headline→theme.headline - MediaQuery Shorthand:
MediaQuery.of(context).size.width→screen.w
Component References (figma2flutter)
children:
- <QuickAcess:Property 1=Default> # Component with variant
- <B-NavBar:Status=Home, Mode=Light> # Multi-property variant
- <B-NavBar> # Component without variant
Component tags (<>) are generated by the figma2flutter plugin. Full resolution requires the MCP server. Offline reverse produces Text('ComponentName (Variant)') placeholders with a warning.
Collection Elements
children:
- ...items # Spread operator
- for(item in products): ProductCard: {} # For-in loop
- if _isLoading: CircularProgressIndicator # Conditional
- if _isLoading: Spinner: {} # Conditional with alternative
else: Text: "Done"
Named Child Slots
Any single-widget slot is a key of its own; list-valued slots keep their own name so the reverse converter emits the argument the widget actually declares.
Scaffold:
appBar:
AppBar:
title:
Text: "Home"
actions: # not `children:` — AppBar has none
- Icon: search
body:
Center: { }
BottomNavigationBar:
items: [] # empty lists are preserved
ListView.builder:
itemCount: items.length
itemBuilder: # rebuilt as (context, index) => …
ProductCard: { }
Switch Arms
A switch in a build method becomes a switch(...) node with one arm per case:
build:
switch(status):
- case Status.loading: CircularProgressIndicator: {}
- case Status.error: Text: "failed"
- default: HomeView: {}
Raw Expression Escape Hatch
Anything the compact format has no shape for is preserved verbatim rather than dropped, and round-trips unchanged:
build:
expr: _buildBody(context)
Multiple Widget Classes Per File
A .dart file declaring several widget classes produces one YAML document per
class, separated by ---. Reversing such a file writes all classes back into a
single .dart with the imports emitted once.
widget: Header
type: StatelessWidget
build:
Text: title
---
widget: Footer
type: StatelessWidget
build:
Text: "footer"
Generic Widgets and Top-Level Declarations
Generic widget classes keep their type parameters, and non-widget declarations
the widget refers to (enums, typedefs) travel with it at --level full:
widget: Boxed<T extends Comparable<T>>
type: StatelessWidget
declarations:
- enum Status {loading, error, done}
Installation
dart pub global activate flutter_2yaml
Or add to your project's dev_dependencies:
dev_dependencies:
flutter_2yaml: ^0.6.0
Requires Dart 3.11 or newer (the analyzer package this tool is built on
requires it).
Usage
CLI — Forward (Dart → YAML)
# Convert a single file
flutter_2yaml lib/screens/splash_screen.dart
# Convert all .dart files in a directory
flutter_2yaml lib/screens/ --recursive
# Choose verbosity level
flutter_2yaml lib/screens/ --level minimal # Widget tree only
flutter_2yaml lib/screens/ --level standard # + state & lifecycle (default)
flutter_2yaml lib/screens/ --level full # + imports, constructor, methods
# Custom output directory
flutter_2yaml lib/screens/ --output yaml_output/
# Watch mode — auto-regenerate on file changes
flutter_2yaml lib/screens/ --watch
CLI — Reverse (YAML → Dart)
# Convert a single YAML file back to Dart
flutter_2yaml reverse splash_screen.yaml
# Convert a directory of YAML files
flutter_2yaml reverse yaml_output/ --recursive
# Custom output directory
flutter_2yaml reverse yaml_output/ --output lib/screens/
Programmatic API
import 'package:flutter_2yaml/flutter_2yaml.dart';
// Forward: Dart → YAML
final analyzer = DartFileAnalyzer();
final model = analyzer.analyze(dartSource, 'my_widget.dart');
final yamlGenerator = YamlGenerator();
final yaml = yamlGenerator.generate(model!, VerbosityLevel.standard);
// Reverse: YAML → Dart
final yamlParser = YamlParser();
final reverseModel = yamlParser.parse(yamlSource, 'my_widget.yaml');
final dartGenerator = DartGenerator();
final dartCode = dartGenerator.generate(reverseModel);
// Files declaring several widget classes: one model per class
final models = analyzer.analyzeAll(dartSource, 'my_widgets.dart');
final multiYaml = yamlGenerator.generateAll(models, VerbosityLevel.standard);
final reverseModels = yamlParser.parseAll(multiYaml, 'my_widgets.yaml');
final multiDart = dartGenerator.generateAll(reverseModels);
// File-level conversion (handles multi-class files automatically)
final converter = Converter();
converter.convertFile('lib/screens/home.dart');
final reverseConverter = ReverseConverter();
reverseConverter.reverseFile('home.yaml');
Verbosity Levels
| Level | Includes | Use Case |
|---|---|---|
minimal |
Widget tree + shorthands | Quick UI structure overview |
standard |
+ state, lifecycle | Full widget understanding (default) |
full |
+ imports, constructor, all methods, enums/typedefs | Complete file representation — use this for a round trip |
Supported Widget Types
StatelessWidget,StatefulWidgetConsumerWidget,ConsumerStatefulWidget(Riverpod)GetView,GetWidget(GetX)- Non-widget classes are automatically skipped
Supported Widget Features
- Scaffold with all named slots (appBar, drawer, FAB, bottomNav, etc.)
- Container with BoxDecoration (bg, br, shadow, gradient, border, shape)
- Stack + Positioned with coordinate shorthands
- ListView.builder / GridView.builder (itemBuilder extraction)
- Wrap with spacing shorthands
- Text with full TextStyle pipe syntax
- Conditional children (
if/if-else) - Spread and for-in elements in children lists
- All callback types with arrow notation
switchexpressions and statements inbuild()- Multiple widget classes per file (one
---document each) - Generic widget classes, with type parameters preserved
- Colors as
Colors.x,#RRGGBB, or#AARRGGBB(alpha preserved) theme.*andscreen.*context lookups
Known Limitations
The compact format is a summary, so some detail is deliberately not carried:
- Method bodies are summarised, not preserved.
initStatebecomes a list of action descriptions; on the way back, actions that dropped information (an assignment, a call whose arguments were elided, control flow) are emitted as comments rather than as code that would not compile. - Callback bodies with more than one statement are reduced to an empty
lambda of the right arity (
validator → (value) {}). - Only enums and typedefs are carried among non-widget top-level
declarations, and only at
--level full; other top-level code is skipped. - Field mutability is not recorded — a
finalstate field comes back non-final. minimalandstandardomit imports and constructors by design, so reversing at those levels produces a widget without its constructor parameters. Use--level fullfor a round trip that preserves the class surface.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
MIT License — see LICENSE for details.
Libraries
- flutter_2yaml
- Convert Flutter .dart widget files into compact YAML representations optimized for LLM/AI token consumption.