UI Theming Skill
Purpose
Apply brand-specific or reference-based UI designs to Flutter apps. This skill guides the process from analyzing reference images to implementing a cohesive design system.
Workflow Overview
- Analyze Reference → 2. Extract Design System → 3. Implement Theme → 4. Layout Changes → 5. Verify
Phase 1: Analyze Reference Images
When the user provides reference images or mentions a brand style:
Extract from Images
-
Color Palette - Primary, secondary, accent, background, surface colors
-
Typography - Font families, weights, sizes
-
Border Radius - Buttons, cards, inputs, badges
-
Component Patterns - Buttons, cards, badges, banners, FAB styles
-
Spacing - Padding, margins, gaps
Document Findings
Create a design specification table:
| Element | Light Mode | Dark Mode | Notes |
|---|---|---|---|
| Primary | #XXXXXX | #XXXXXX | Main action color |
| Secondary | #XXXXXX | #XXXXXX | Accent color |
| Background | #FFFFFF | #1A1A1A | App background |
| Surface | #F8F8F8 | #2D2D2D | Card background |
Phase 2: Implement Theme Files
File Structure
lib/core/theme/ ├── app_colors.dart # Color palette & ColorScheme ├── app_typography.dart # Font families & TextTheme ├── app_radius.dart # BorderRadius presets ├── app_spacing.dart # Spacing constants └── app_theme.dart # ThemeData integration
app_colors.dart Pattern
import 'package:flutter/material.dart';
/// Brand color palette class AppColors { // Primary colors static const Color primaryLight = Color(0xFFXXXXXX); static const Color primaryDark = Color(0xFFXXXXXX);
// Secondary colors static const Color secondaryLight = Color(0xFFXXXXXX); static const Color secondaryDark = Color(0xFFXXXXXX);
// Supporting colors static const Color errorLight = Color(0xFFDC3545); static const Color successLight = Color(0xFF28A745); }
/// ColorScheme builder class AppColorScheme { static ColorScheme lightScheme() { return ColorScheme.light( primary: AppColors.primaryLight, secondary: AppColors.secondaryLight, // ... other colors ); }
static ColorScheme darkScheme() { return ColorScheme.dark( primary: AppColors.primaryDark, secondary: AppColors.secondaryDark, // ... other colors ); } }
app_typography.dart Pattern
import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart';
class AppTypography { // Font families static String get headlineFont => GoogleFonts.poppins().fontFamily!; static String get bodyFont => GoogleFonts.nunitoSans().fontFamily!;
static TextTheme buildTextTheme(ColorScheme colorScheme) { return TextTheme( headlineLarge: GoogleFonts.poppins( fontSize: 32, fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), // ... other text styles ); } }
app_radius.dart Pattern
import 'package:flutter/material.dart';
class AppRadius { // Base values static const double xs = 4.0; static const double sm = 8.0; static const double md = 12.0; static const double lg = 16.0;
// Component-specific presets static const BorderRadius button = BorderRadius.all(Radius.circular(sm)); static const BorderRadius card = BorderRadius.all(Radius.circular(md)); static const BorderRadius input = BorderRadius.all(Radius.circular(sm)); static const BorderRadius badge = BorderRadius.all(Radius.circular(xs)); static const BorderRadius fab = BorderRadius.all(Radius.circular(100)); }
app_theme.dart Pattern
class AppTheme { static ThemeData get lightTheme { final colorScheme = AppColorScheme.lightScheme(); return _buildTheme(colorScheme, Brightness.light); }
static ThemeData get darkTheme { final colorScheme = AppColorScheme.darkScheme(); return _buildTheme(colorScheme, Brightness.dark); }
static ThemeData _buildTheme(ColorScheme colorScheme, Brightness brightness) { return ThemeData( useMaterial3: true, colorScheme: colorScheme, // Component themes... floatingActionButtonTheme: FloatingActionButtonThemeData( backgroundColor: colorScheme.secondary, foregroundColor: colorScheme.onSecondary, ), // ... other component themes ); } }
Phase 3: Layout Changes
Beyond theme colors, consider these layout enhancements:
Promotional Banners
Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [ colorScheme.secondary, colorScheme.secondary.withOpacity(0.8), ], ), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(Icons.local_offer), // Content... ], ), )
Priority Badges
Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: badgeColor.withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(badgeIcon, size: 14, color: badgeColor), SizedBox(width: 4), Text(badgeText, style: TextStyle(color: badgeColor)), ], ), )
Section Cards with Left Border Accent
Container( decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(12), border: Border( left: BorderSide(color: accentColor, width: 4), ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.06), blurRadius: 8, offset: Offset(0, 2), ), ], ), )
Phase 4: Verification
Testing Checklist
Light Theme
-
Colors match reference
-
Typography is correct
-
Border radius is consistent
-
Component styles match
Dark Theme
-
Colors adapt correctly
-
Contrast is sufficient
-
No hard-coded colors
Components
-
Buttons (primary, secondary, text)
-
Cards and surfaces
-
Input fields
-
FAB
-
Badges/tags
-
Navigation
Screenshot Workflow
- Dart MCP: launch_app
- Dart MCP: connect_dart_tooling_daemon
- Navigate to each screen
- Maestro: take_screenshot
- Compare with reference
Common Patterns
Brand Style Examples
Brand Style Primary Secondary Radius Typography
McDonald's Red #DA291C Yellow #FFC72C 8-12px Poppins + Nunito Sans
Discord Blurple #5865F2 Green #57F287 Pill (full) gg sans
Material You Dynamic Dynamic 12-28px Roboto
Google Fonts Pairings
Style Headlines Body
Modern Poppins Nunito Sans
Classic Playfair Display Source Sans Pro
Tech Inter Inter
Friendly Nunito Open Sans
Tips
-
Start with colors - They have the biggest visual impact
-
Use ColorScheme - Material 3 handles light/dark automatically
-
Consistent radius - Pick 2-3 values and stick with them
-
Test both themes - Implement light and dark from the start
-
Layout last - Change component layouts after theme is stable
API Image Limit Handling
Problem
At least one of the image dimensions exceed max allowed size for many-image requests: 2000 pixels
When analyzing many reference images + taking E2E screenshots, the API limit can be exceeded.
Solution: Use Subagents for Image-Heavy Tasks
Phase 1: Reference Image Analysis (Explore Subagent)
Task(subagent_type="Explore", prompt=""" Analyze the reference images in {ui_pocket_path} and extract:
- Layout structure (sidebar, navigation, main area)
- Color scheme (primary, secondary, background colors)
- Component details (buttons, cards, lists, inputs)
- Distinctive UI patterns
Return results as TEXT only. Do not include images in response. """)
Benefits:
-
Subagent context is isolated
-
Images don't accumulate in main context
-
Results returned as text
Phase 5: E2E Testing (Subagent)
Task(subagent_type="general-purpose", prompt=""" Test the app on iOS simulator:
- Launch app (Dart MCP: launch_app)
- Login screen → Quick Login
- Verify TODO list screen
- Verify Settings screen
- Report issues as TEXT
Do NOT take screenshots unless absolutely necessary. Use inspect_view_hierarchy instead (lightweight). """)
Image Management Guidelines
Phase Image Handling
Reference analysis Subagent (return as text)
Component implementation No images
Screen rebuild No images
E2E testing Subagent or minimal
PR screenshots Save to file only
Best Practices
-
Prefer hierarchy over screenshots - inspect_view_hierarchy is lightweight
-
Save screenshots to files - Don't include in conversation
-
Use subagents for image tasks - Isolate context
-
One screenshot per verification - Not multiple
-
Compact conversation - Use /compact if images accumulate