vgv-material-theming
Theming
Material 3 theming best practices for Flutter applications using ThemeData as the single source of truth for colors, typography, component styles, and spacing.
Core Standards
Apply these standards to ALL theming work:
- Use
ThemeDataas the single source of truth — never inline colors or text styles in widgets - Reference colors via
Theme.of(context).colorScheme— neverColors.blue,Colors.red, or any hardcodedColorvalues - Reference text styles via
Theme.of(context).textTheme— never inlineTextStyle(...)in widget code - Use
ColorSchemefor all color definitions — Material 3's structured color system - Centralize component themes in
ThemeData— defineFilledButtonThemeData,InputDecorationTheme, etc. in the theme, not per-widget - Define a spacing system with a base unit — no arbitrary pixel values for padding, margins, or gaps
- Support light and dark themes from the start — use
ThemeDataso theme switching requires zero conditional logic in widgets - Avoid conditional logic for theming in UI — never check brightness in widget code; let
ThemeDatahandle it - Prefer
EdgeInsets.onlyandEdgeInsets.symmetric— neverEdgeInsets.fromLTRB(positional arguments are error-prone)
Color System
Custom Colors Class
Centralize all color definitions in a dedicated class:
abstract class AppColors {
static const primaryColor = Color(0xFF4F46E5);
static const secondaryColor = Color(0xFF9C27B0);
static const errorColor = Color(0xFFDC2626);
static const surfaceColor = Color(0xFFFAFAFA);
}
ColorScheme Configuration
The ColorScheme class includes 45 colors based on Material 3 specifications. Configure it within ThemeData:
ThemeData(
colorScheme: ColorScheme(
brightness: Brightness.light,
primary: AppColors.primaryColor,
secondary: AppColors.secondaryColor,
error: AppColors.errorColor,
surface: AppColors.surfaceColor,
onPrimary: Colors.white,
onSecondary: Colors.white,
onError: Colors.white,
onSurface: Colors.black,
),
)
For quick prototyping, use ColorScheme.fromSeed():
ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: AppColors.primaryColor,
),
)
Light and Dark Theme Variants
class AppTheme {
static ThemeData get light => ThemeData(
colorScheme: ColorScheme(
brightness: Brightness.light,
primary: AppColors.primaryColor,
surface: AppColors.surfaceColor,
// ... remaining color roles
),
);
static ThemeData get dark => ThemeData(
colorScheme: ColorScheme(
brightness: Brightness.dark,
primary: AppColors.primaryColorDark,
surface: AppColors.surfaceColorDark,
// ... remaining color roles
),
);
}
Accessing Colors
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return ColoredBox(
color: colorScheme.surface,
child: Text(
'Hello',
style: TextStyle(color: colorScheme.onSurface),
),
);
}
Typography
Font Asset Organization
Store fonts in assets/fonts/ and declare them in pubspec.yaml:
flutter:
fonts:
- family: Inter
fonts:
- asset: assets/fonts/Inter-Regular.ttf
weight: 400
- asset: assets/fonts/Inter-Medium.ttf
weight: 500
- asset: assets/fonts/Inter-Bold.ttf
weight: 700
Use flutter_gen to generate type-safe font family constants:
class FontFamily {
static const String inter = 'Inter';
}
Custom Text Styles Class
Centralize text style definitions for consistent updates:
abstract class AppTextStyle {
static const _baseStyle = TextStyle(
fontFamily: FontFamily.inter,
fontWeight: FontWeight.w400,
);
static final TextStyle displayLarge = _baseStyle.copyWith(
fontSize: 57,
height: 1.12,
fontWeight: FontWeight.w400,
);
static final TextStyle headlineMedium = _baseStyle.copyWith(
fontSize: 28,
height: 1.29,
fontWeight: FontWeight.w400,
);
static final TextStyle titleLarge = _baseStyle.copyWith(
fontSize: 20,
height: 1.3,
fontWeight: FontWeight.w500,
);
static final TextStyle bodyLarge = _baseStyle.copyWith(
fontSize: 16,
height: 1.5,
fontWeight: FontWeight.w400,
);
static final TextStyle labelLarge = _baseStyle.copyWith(
fontSize: 14,
height: 1.43,
fontWeight: FontWeight.w500,
);
}
TextTheme Integration
Integrate custom styles into ThemeData.textTheme:
ThemeData(
textTheme: TextTheme(
displayLarge: AppTextStyle.displayLarge,
headlineMedium: AppTextStyle.headlineMedium,
titleLarge: AppTextStyle.titleLarge,
bodyLarge: AppTextStyle.bodyLarge,
labelLarge: AppTextStyle.labelLarge,
),
)
Accessing Text Styles
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Text(
'Hello',
style: textTheme.headlineMedium,
);
}
Component Themes
Material components use colorScheme and textTheme by default, but each widget has a customizable theme. Define component themes centrally in ThemeData instead of styling individual widget instances.
FilledButton
ThemeData(
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
minimumSize: const Size(72, 48),
textStyle: AppTextStyle.labelLarge,
),
),
)
InputDecoration
ThemeData(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.md,
),
),
)
AppBar
ThemeData(
appBarTheme: AppBarTheme(
centerTitle: true,
elevation: 0,
titleTextStyle: AppTextStyle.titleLarge,
),
)
Spacing System
Define a spacing system using a base unit to ensure consistency and avoid hardcoded values:
abstract class AppSpacing {
static const double spaceUnit = 16;
/// 4px
static const double xxs = 0.25 * spaceUnit;
/// 6px
static const double xs = 0.375 * spaceUnit;
/// 8px
static const double sm = 0.5 * spaceUnit;
/// 12px
static const double md = 0.75 * spaceUnit;
/// 16px
static const double lg = spaceUnit;
/// 24px
static const double xlg = 1.5 * spaceUnit;
/// 32px
static const double xxlg = 2 * spaceUnit;
}
Applying Spacing
Padding(
padding: EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.md,
),
child: Column(
spacing: AppSpacing.sm,
children: [
// widgets
],
),
)
EdgeInsets Preferences
Prefer EdgeInsets.only (Named Parameters)
// Preferred — clear which side each value applies to
EdgeInsets.only(top: 16, bottom: 8)
Prefer EdgeInsets.symmetric
// Preferred — concise when horizontal or vertical values match
EdgeInsets.symmetric(horizontal: 16, vertical: 8)
Avoid EdgeInsets.fromLTRB
// Avoid — positional arguments make it easy to mix up sides
EdgeInsets.fromLTRB(16, 8, 16, 8)
Complete Theme Example
class AppTheme {
static ThemeData get light {
final colorScheme = ColorScheme(
brightness: Brightness.light,
primary: AppColors.primaryColor,
secondary: AppColors.secondaryColor,
error: AppColors.errorColor,
surface: AppColors.surfaceColor,
onPrimary: Colors.white,
onSecondary: Colors.white,
onError: Colors.white,
onSurface: Colors.black,
);
return ThemeData(
colorScheme: colorScheme,
textTheme: TextTheme(
displayLarge: AppTextStyle.displayLarge,
headlineMedium: AppTextStyle.headlineMedium,
titleLarge: AppTextStyle.titleLarge,
bodyLarge: AppTextStyle.bodyLarge,
labelLarge: AppTextStyle.labelLarge,
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
minimumSize: const Size(72, 48),
textStyle: AppTextStyle.labelLarge,
),
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
appBarTheme: AppBarTheme(
centerTitle: true,
elevation: 0,
titleTextStyle: AppTextStyle.titleLarge,
),
);
}
}
Using the Theme
MaterialApp(
theme: AppTheme.light,
darkTheme: AppTheme.dark,
home: const HomePage(),
)
Accessing Theme Properties in Widgets
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final textTheme = theme.textTheme;
return ColoredBox(
color: colorScheme.surface,
child: Text('Good', style: textTheme.bodyLarge),
);
}
Common Patterns
Creating a Theme
- Define
AppColorswith all color constants - Define
AppTextStylewith all text style constants - Define
AppSpacingwith spacing scale based on a base unit - Create
AppThemeclass withlightanddarkgetters - Configure
ColorScheme,TextTheme, and component themes in eachThemeData - Pass
AppTheme.lightandAppTheme.darktoMaterialApp
Adding a New Color Token
- Add the color constant to
AppColors - Map it to the appropriate
ColorSchemerole (or create a theme extension for custom tokens) - Reference it via
Theme.of(context).colorScheme.<role>in widgets
Dark Mode Support
- Create separate
ColorSchemeinstances for light and dark - Use the same
TextThemeand component themes (they adapt automatically viacolorScheme) - Pass both themes to
MaterialAppviathemeanddarkTheme - Never check
Brightnessin widget code — letThemeDatahandle the switch
Quick Reference
| ThemeData Property | Purpose |
|---|---|
colorScheme |
Material 3 color system (45 color roles) |
textTheme |
Typography scale (display, headline, body…) |
filledButtonTheme |
FilledButton default style |
inputDecorationTheme |
TextField/TextFormField decoration defaults |
appBarTheme |
AppBar default styling |
cardTheme |
Card default styling |
dialogTheme |
Dialog default styling |
| Material 3 Color Role | Typical Use |
|---|---|
primary |
Key UI elements, FAB, active states |
onPrimary |
Text/icons on primary color |
secondary |
Less prominent UI elements |
surface |
Card, sheet, dialog backgrounds |
onSurface |
Text/icons on surface color |
error |
Error indicators, destructive actions |
outline |
Borders, dividers |
More from verygoodopensource/very_good_ai_flutter_plugin
vgv-bloc
Best practices for Bloc state management in Flutter/Dart. Use when writing, modifying, or reviewing code that uses package:bloc, package:flutter_bloc, or package:bloc_test.
5vgv-testing
Best practices for Dart unit tests, Flutter widget tests, and golden file tests. Use when writing, modifying, or reviewing tests that use package:test, package:flutter_test, package:mocktail, or package:bloc_test.
4vgv-navigation
Best practices for navigation and routing in Flutter using GoRouter. Use when creating, modifying, or reviewing routes, deep links, redirects, or navigation logic that uses package:go_router or package:go_router_builder.
4vgv-accessibility
Flutter accessibility auditing and remediation with WCAG 2.1 level selection (A, AA, AAA) across mobile, desktop, and web platforms. Use when building, auditing, or reviewing widgets for screen reader support, touch targets, focus management, color contrast, text scaling, or motion sensitivity. Begins by asking the WCAG conformance level and target platform(s) before applying level-appropriate, platform-aware criteria.
4vgv-create-project
Scaffold a new Dart or Flutter project from a Very Good CLI template. Use when user says "create a new project", "start a new flutter app", "scaffold a package", "initialize a dart cli", "new flame game", or "generate a plugin". Supports flutter_app, dart_package, flutter_package, flutter_plugin, dart_cli, flame_game, and docs_site templates.
4vgv-license-compliance
>
4