Flutter UI System
Philosophy: Flutter owns every pixel. Const-first. Theme-driven. GPU-animated. State-scoped.
🔧 Runtime Scripts
| Script |
Purpose |
Usage |
scripts/flutter_ui_audit.py |
Audit: missing const, dispose leaks, deprecated APIs |
python scripts/flutter_ui_audit.py <path> |
🔴 MANDATORY: Read Reference Files First
⛔ DO NOT code until relevant files are read:
| File |
Content |
Priority |
| flutter-design-thinking.md |
Anti-memorization, forces context analysis |
⬜ CRITICAL FIRST |
| flutter-state-ui.md |
Riverpod + Provider rules Claude MUST enforce |
⬜ CRITICAL |
| flutter-navigation.md |
GoRouter, PopScope, back logic, deep links |
⬜ CRITICAL |
| flutter-animations.md |
Implicit, explicit, Hero, page transitions, physics |
⬜ CRITICAL |
| flutter-performance.md |
const, RepaintBoundary, 60fps, shader warmup |
⬜ CRITICAL |
| flutter-theme-system.md |
Material 3, ColorScheme, TextTheme, ThemeExtension |
⬜ Read |
| flutter-layout-system.md |
Responsive, LayoutBuilder, MediaQuery, Slivers |
⬜ Read |
| flutter-custom-paint.md |
Canvas, CustomPainter, pixel-perfect rendering |
⬜ Read |
🧠 flutter-design-thinking.md FIRST — prevents AI from applying memorized patterns.
⚠️ ASK BEFORE ASSUMING
| Aspect |
Ask |
| State manager |
"Riverpod, Provider, BLoC, or vanilla?" |
| Navigation |
"GoRouter, auto_route, or Navigator 1.0?" |
| Design system |
"Material 3, Cupertino, or custom?" |
| Platforms |
"Mobile only, or tablet/desktop adaptive?" |
| Flutter version |
"3.12+? (PopScope vs WillPopScope)" |
⛔ ANTI-PATTERNS
Performance
| ❌ NEVER |
✅ ALWAYS |
ListView with static children for long lists |
ListView.builder |
setState in animation loop |
AnimatedBuilder |
Opacity(opacity: 0) to hide |
Visibility or conditional render |
IntrinsicHeight/IntrinsicWidth in lists |
Fixed heights or SliverFixedExtentList |
Missing const on StatelessWidget |
const MyWidget({super.key}) always |
Column + ListView without Expanded |
Wrap ListView in Expanded |
Hardcoded Color(0xFF...) in widgets |
Theme.of(context).colorScheme.primary |
Hardcoded fontSize |
Theme.of(context).textTheme.bodyLarge |
Animation
| ❌ NEVER |
✅ ALWAYS |
Animate width/height |
Animate Transform (scale/translate) |
AnimationController without dispose() |
Dispose in dispose() method |
addListener(() => setState((){})) |
AnimatedBuilder |
Missing RepaintBoundary on complex painters |
Wrap CustomPaint in RepaintBoundary |
| Hero tag collision |
Unique data-driven tags |
| No easing curve |
Curves.easeInOut minimum |
Navigation
| ❌ NEVER |
✅ ALWAYS |
WillPopScope (Flutter 3.12+ deprecated) |
PopScope with canPop + onPopInvokedWithResult |
Navigator.push in GoRouter app |
context.go() / context.push() |
| Hardcoded route strings everywhere |
Centralized route constants |
| No route guard for auth screens |
GoRouter redirect callback |
| Back button does nothing at root |
PopScope(canPop: false) + exit dialog |
State
| ❌ NEVER |
✅ ALWAYS |
ref.read() in build() (Riverpod) |
ref.watch() in build() |
Provider defined inside build() |
Top-level final myProvider = ... |
context.read<T>() in build() (Provider) |
context.watch<T>() in build() |
BuildContext in ChangeNotifier |
Pass data, not context |
notifyListeners() before mutation |
Mutate first, then notifyListeners() |
.when() without error handler |
Always when(data:, loading:, error:) |
Consumer<T> wrapping entire screen |
Selector<T, R> on smallest widget |
🗺️ Navigation: go() vs push() vs replace()
| Method |
Back Stack |
Use When |
context.go('/path') |
Replaces stack |
Tab switching, auth redirect |
context.push('/path') |
Adds to stack |
Drill-down navigation |
context.replace('/path') |
Replaces current, no pop |
Login → Home after auth |
context.pop() |
Removes current |
Back button, close modal |
context.pop(result) |
Removes + returns data |
Form submit, picker result |
🎬 Animation Selection
WHAT ARE YOU ANIMATING?
├── Simple property change → AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder
├── Complex sequence/stagger → AnimationController + CurvedAnimation + AnimatedBuilder
├── Shared element (cross-screen) → Hero widget + unique tag
├── Page enter/exit → GoRouter CustomTransitionPage or pageBuilder
├── Spring/bounce/momentum → SpringSimulation, BouncingScrollPhysics
└── Vector/sprite → Rive or Lottie package
GPU-safe: transform, opacity → smooth
CPU-bound: width, height, margin, padding → jank
📊 State Manager Auto-Detect
Scan imports before writing any state code:
| Import Found |
Use |
flutter_riverpod |
Riverpod rules |
provider package |
Provider rules |
| Neither |
Ask user → recommend Riverpod for new projects |
⚡ Performance Quick Reference
class MyCard extends StatelessWidget {
const MyCard({super.key});
@override
Widget build(BuildContext context) => const Padding(
padding: EdgeInsets.all(16),
child: Text('Hello'),
);
}
RepaintBoundary(child: AnimatedWidget(...))
ListView.builder(itemCount: n, itemBuilder: (ctx, i) => Item(items[i]))
final name = ref.watch(userProvider.select((u) => u.name));
🧠 CHECKPOINT (Fill Before Any Flutter Work)
🧠 FLUTTER CHECKPOINT:
State: [ Riverpod / Provider / BLoC / Vanilla ]
Navigation: [ GoRouter / auto_route / Navigator 1.0 ]
Design: [ Material 3 / Cupertino / Custom ]
Platforms: [ Mobile / Tablet / Desktop / Web ]
Files Read: [ List files read ]
3 Rules I Will Apply:
1. _______________
2. _______________
3. _______________
Anti-Patterns I Will Avoid:
1. _______________
2. _______________
🔴 Can't fill checkpoint? → Read the skill files.
📋 Checklists
Per Screen
Per Animation
Pre-Release
📚 Reference Files
| File |
Use When |
| flutter-design-thinking.md |
FIRST — prevents AI defaults |
| flutter-state-ui.md |
Riverpod + Provider rules, patterns, migration |
| flutter-navigation.md |
GoRouter, back logic, deep links, tabs |
| flutter-animations.md |
Implicit, explicit, Hero, transitions, physics |
| flutter-theme-system.md |
Material 3, ColorScheme, TextTheme |
| flutter-layout-system.md |
Responsive, LayoutBuilder, Slivers |
| flutter-performance.md |
const, RepaintBoundary, DevTools |
| flutter-custom-paint.md |
Canvas, CustomPainter, pixel-perfect |
Pixel-perfect = every spacing, color, and type choice is intentional and theme-driven. Own the pixels through your design system, not magic numbers.
1---2name: flutter-ui3description: Pixel-perfect Flutter UI — smooth animations, GoRouter navigation, back/forth logic, Riverpod + Provider state management. Use when building Flutter screens, navigation flows, animations, or state-connected widgets.4---56# Flutter UI System78> **Philosophy:** Flutter owns every pixel. Const-first. Theme-driven. GPU-animated. State-scoped.910---1112## 🔧 Runtime Scripts1314| Script | Purpose | Usage |15|--------|---------|-------|16| `scripts/flutter_ui_audit.py` | Audit: missing const, dispose leaks, deprecated APIs | `python scripts/flutter_ui_audit.py <path>` |1718---1920## 🔴 MANDATORY: Read Reference Files First2122**⛔ DO NOT code until relevant files are read:**2324| File | Content | Priority |25|------|---------|----------|26| **[flutter-design-thinking.md](flutter-design-thinking.md)** | Anti-memorization, forces context analysis | **⬜ CRITICAL FIRST** |27| **[flutter-state-ui.md](flutter-state-ui.md)** | Riverpod + Provider rules Claude MUST enforce | **⬜ CRITICAL** |28| **[flutter-navigation.md](flutter-navigation.md)** | GoRouter, PopScope, back logic, deep links | **⬜ CRITICAL** |29| **[flutter-animations.md](flutter-animations.md)** | Implicit, explicit, Hero, page transitions, physics | **⬜ CRITICAL** |30| **[flutter-performance.md](flutter-performance.md)** | const, RepaintBoundary, 60fps, shader warmup | **⬜ CRITICAL** |31| [flutter-theme-system.md](flutter-theme-system.md) | Material 3, ColorScheme, TextTheme, ThemeExtension | ⬜ Read |32| [flutter-layout-system.md](flutter-layout-system.md) | Responsive, LayoutBuilder, MediaQuery, Slivers | ⬜ Read |33| [flutter-custom-paint.md](flutter-custom-paint.md) | Canvas, CustomPainter, pixel-perfect rendering | ⬜ Read |3435> 🧠 **flutter-design-thinking.md FIRST** — prevents AI from applying memorized patterns.3637---3839## ⚠️ ASK BEFORE ASSUMING4041| Aspect | Ask |42|--------|-----|43| **State manager** | "Riverpod, Provider, BLoC, or vanilla?" |44| **Navigation** | "GoRouter, auto_route, or Navigator 1.0?" |45| **Design system** | "Material 3, Cupertino, or custom?" |46| **Platforms** | "Mobile only, or tablet/desktop adaptive?" |47| **Flutter version** | "3.12+? (PopScope vs WillPopScope)" |4849---5051## ⛔ ANTI-PATTERNS5253### Performance5455| ❌ NEVER | ✅ ALWAYS |56|----------|-----------|57| `ListView` with static children for long lists | `ListView.builder` |58| `setState` in animation loop | `AnimatedBuilder` |59| `Opacity(opacity: 0)` to hide | `Visibility` or conditional render |60| `IntrinsicHeight`/`IntrinsicWidth` in lists | Fixed heights or `SliverFixedExtentList` |61| Missing `const` on StatelessWidget | `const MyWidget({super.key})` always |62| `Column` + `ListView` without `Expanded` | Wrap `ListView` in `Expanded` |63| Hardcoded `Color(0xFF...)` in widgets | `Theme.of(context).colorScheme.primary` |64| Hardcoded `fontSize` | `Theme.of(context).textTheme.bodyLarge` |6566### Animation6768| ❌ NEVER | ✅ ALWAYS |69|----------|-----------|70| Animate `width`/`height` | Animate `Transform` (scale/translate) |71| `AnimationController` without `dispose()` | Dispose in `dispose()` method |72| `addListener(() => setState((){}))` | `AnimatedBuilder` |73| Missing `RepaintBoundary` on complex painters | Wrap `CustomPaint` in `RepaintBoundary` |74| Hero tag collision | Unique data-driven tags |75| No easing curve | `Curves.easeInOut` minimum |7677### Navigation7879| ❌ NEVER | ✅ ALWAYS |80|----------|-----------|81| `WillPopScope` (Flutter 3.12+ deprecated) | `PopScope` with `canPop` + `onPopInvokedWithResult` |82| `Navigator.push` in GoRouter app | `context.go()` / `context.push()` |83| Hardcoded route strings everywhere | Centralized route constants |84| No route guard for auth screens | GoRouter `redirect` callback |85| Back button does nothing at root | `PopScope(canPop: false)` + exit dialog |8687### State8889| ❌ NEVER | ✅ ALWAYS |90|----------|-----------|91| `ref.read()` in `build()` (Riverpod) | `ref.watch()` in `build()` |92| Provider defined inside `build()` | Top-level `final myProvider = ...` |93| `context.read<T>()` in `build()` (Provider) | `context.watch<T>()` in `build()` |94| `BuildContext` in `ChangeNotifier` | Pass data, not context |95| `notifyListeners()` before mutation | Mutate first, then `notifyListeners()` |96| `.when()` without error handler | Always `when(data:, loading:, error:)` |97| `Consumer<T>` wrapping entire screen | `Selector<T, R>` on smallest widget |9899---100101## 🗺️ Navigation: go() vs push() vs replace()102103| Method | Back Stack | Use When |104|--------|-----------|----------|105| `context.go('/path')` | Replaces stack | Tab switching, auth redirect |106| `context.push('/path')` | Adds to stack | Drill-down navigation |107| `context.replace('/path')` | Replaces current, no pop | Login → Home after auth |108| `context.pop()` | Removes current | Back button, close modal |109| `context.pop(result)` | Removes + returns data | Form submit, picker result |110111---112113## 🎬 Animation Selection114115```116WHAT ARE YOU ANIMATING?117├── Simple property change → AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder118├── Complex sequence/stagger → AnimationController + CurvedAnimation + AnimatedBuilder119├── Shared element (cross-screen) → Hero widget + unique tag120├── Page enter/exit → GoRouter CustomTransitionPage or pageBuilder121├── Spring/bounce/momentum → SpringSimulation, BouncingScrollPhysics122└── Vector/sprite → Rive or Lottie package123```124125**GPU-safe:** `transform`, `opacity` → smooth126**CPU-bound:** `width`, `height`, `margin`, `padding` → jank127128---129130## 📊 State Manager Auto-Detect131132Scan imports before writing any state code:133134| Import Found | Use |135|-------------|-----|136| `flutter_riverpod` | Riverpod rules |137| `provider` package | Provider rules |138| Neither | Ask user → recommend Riverpod for new projects |139140---141142## ⚡ Performance Quick Reference143144```dart145class MyCard extends StatelessWidget {146 const MyCard({super.key});147 @override148 Widget build(BuildContext context) => const Padding(149 padding: EdgeInsets.all(16),150 child: Text('Hello'),151 );152}153154RepaintBoundary(child: AnimatedWidget(...))155156ListView.builder(itemCount: n, itemBuilder: (ctx, i) => Item(items[i]))157158final name = ref.watch(userProvider.select((u) => u.name));159```160161---162163## 🧠 CHECKPOINT (Fill Before Any Flutter Work)164165```166🧠 FLUTTER CHECKPOINT:167168State: [ Riverpod / Provider / BLoC / Vanilla ]169Navigation: [ GoRouter / auto_route / Navigator 1.0 ]170Design: [ Material 3 / Cupertino / Custom ]171Platforms: [ Mobile / Tablet / Desktop / Web ]172Files Read: [ List files read ]1731743 Rules I Will Apply:1751. _______________1762. _______________1773. _______________178179Anti-Patterns I Will Avoid:1801. _______________1812. _______________182```183184> 🔴 **Can't fill checkpoint? → Read the skill files.**185186---187188## 📋 Checklists189190### Per Screen191- [ ] `ConsumerWidget` if Riverpod? `const` constructor?192- [ ] Touch targets ≥ 48dp?193- [ ] Loading + error states defined?194- [ ] Back navigation tested (PopScope or GoRouter canPop)?195- [ ] No hardcoded colors or font sizes?196197### Per Animation198- [ ] Animating `transform`/`opacity`, not `width`/`height`?199- [ ] `AnimationController.dispose()` called?200- [ ] Duration ≥ 150ms with easing curve?201- [ ] `RepaintBoundary` on complex painters?202203### Pre-Release204- [ ] Run audit script — fix all 🔴 findings205- [ ] `flutter analyze` — no missing const warnings206- [ ] Android back button tested — no stuck screens207- [ ] Dark mode tested — all colors from Theme208- [ ] Tablet layout tested — no overflow209- [ ] All `.when()` handle error state210211---212213## 📚 Reference Files214215| File | Use When |216|------|---------|217| [flutter-design-thinking.md](flutter-design-thinking.md) | **FIRST — prevents AI defaults** |218| [flutter-state-ui.md](flutter-state-ui.md) | Riverpod + Provider rules, patterns, migration |219| [flutter-navigation.md](flutter-navigation.md) | GoRouter, back logic, deep links, tabs |220| [flutter-animations.md](flutter-animations.md) | Implicit, explicit, Hero, transitions, physics |221| [flutter-theme-system.md](flutter-theme-system.md) | Material 3, ColorScheme, TextTheme |222| [flutter-layout-system.md](flutter-layout-system.md) | Responsive, LayoutBuilder, Slivers |223| [flutter-performance.md](flutter-performance.md) | const, RepaintBoundary, DevTools |224| [flutter-custom-paint.md](flutter-custom-paint.md) | Canvas, CustomPainter, pixel-perfect |225226---227228> Pixel-perfect = every spacing, color, and type choice is intentional and theme-driven. Own the pixels through your design system, not magic numbers.