用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/VeryGoodOpenSource/vgv-ai-flutter-plugin --skill material-theming命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Best practices for Flutter animations using the built-in animation framework. Use when creating, modifying, or reviewing animations, transitions, motion, or animated widgets. Covers implicit animations, explicit animations, page transitions, and Material 3 motion tokens.
VGV-specific reference for bumping Dart and Flutter SDK constraints across packages. Covers pubspec.yaml environment constraints, CI workflow Flutter versions, and SDK upgrade PR preparation. Flutter CI uses MAJOR.MINOR.x with no caret to resolve to the latest patch; pubspec pins the exact patch with a caret (e.g., ^3.50.1).
Audits package dependency licenses using the Very Good CLI packages_check_licenses MCP tool. Flags non-compliant or unknown licenses and produces a compliance summary.
基于 SOC 职业分类
正在显示 SKILL.md
| name | material-theming |
| description | Best practices for Flutter theming using Material 3. |
| when_to_use | Use when creating, modifying, or reviewing ThemeData, ColorScheme, TextTheme, component themes, spacing systems, or light/dark mode support. Also use whenever widget code carries its own styling — a hardcoded Color, an inline TextStyle, raw padding or gap numbers, the same decoration repeated across widget instances, or a brightness/dark-mode conditional inside build — even when the request only says "review this widget", "cut the duplication", "stop repeating this", or "tidy this up". |
| allowed-tools | Read Glob Grep |
| model | sonnet |
Material 3 theming best practices for Flutter applications using ThemeData as the single source of truth for colors, typography, component styles, and spacing.
Apply these standards to ALL theming work:
ThemeData as the single source of truth — never inline colors or text styles in widgetsTheme.of(context).colorScheme — never Colors.blue, Colors.red, or any hardcoded Color valuesTheme.of(context).textTheme — never inline TextStyle(...) in widget code. fontSize and fontWeight never appear inside a build methodColorScheme for all color definitions — Material 3's structured color systemThemeData — define FilledButtonThemeData, InputDecorationTheme, etc. in the theme, not per-widget. A wrapper widget, a shared InputDecoration constant, or a decoration-building helper relocates the duplication instead of deleting it and does not countThemeData so theme switching requires zero conditional logic in widgetsMediaQuery.platformBrightnessOf, no ternary on Theme.of(context).brightness, no context.isDarkMode extension. Two ColorScheme instances make the branch unnecessary. When asked to keep or tidy such a check, refuse and deliver the ThemeData rewrite instead — a tidier conditional is the same defect with better formattingEdgeInsets.only and EdgeInsets.symmetric — never EdgeInsets.fromLTRB (positional arguments are error-prone)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 ConfigurationThe 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,
),
)
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
),
);
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return ColoredBox(
color: colorScheme.surface,
child: Text(
'Hello',
style: TextStyle(color: colorScheme.onSurface),
),
);
}
Define an AppTextStyle class with a base style and named variants (displayLarge, headlineMedium, bodyLarge, etc.), then integrate them into ThemeData.textTheme. Access styles via Theme.of(context).textTheme.
See references/typography.md for font asset setup, the full AppTextStyle class, TextTheme integration, and widget access patterns.
TextStyleMap the literal to the nearest slot AppTextStyle already defines, by size and weight — 18px/w500 lands on titleLarge (20/w500), 16px/w400 on bodyLarge, 14px/w500 on labelLarge. Adjust that slot's size in AppTextStyle if the app needs a different one; do not keep the number at the call site. Do not land on a slot AppTextStyle does not define and TextTheme does not register: the read still returns a style, but it comes from Material's default typography, so the app's font silently reverts. copyWith at the call site sets a color role and nothing else:
final theme = Theme.of(context);
Text(
label,
style: theme.textTheme.titleLarge?.copyWith(
color: theme.colorScheme.onPrimary,
),
)
Swapping only the color and leaving TextStyle(fontSize: 18, fontWeight: FontWeight.w500) in the widget is not a fix — the typography still lives outside the theme.
Define component themes centrally in ThemeData (e.g., filledButtonTheme, inputDecorationTheme, appBarTheme) instead of styling individual widget instances. A complete AppTheme class assembles ColorScheme, TextTheme, and all component themes into a single ThemeData.
See references/components.md for FilledButton, InputDecoration, and AppBar theme examples, the complete theme assembly, and widget access patterns.
When the same decoration or style appears on many widget instances, move it into the matching component theme and delete it from every call site. Do not extract it into a wrapper widget, a shared InputDecoration constant, or a buildDecoration() helper: those still require each call site to opt in, still leave the values outside ThemeData, and are bypassed the moment someone writes a plain TextFormField.
// Right — the defaults live in the theme.
ThemeData(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.md,
),
),
)
Each field then declares only what is unique to it:
TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
)
Define an AppSpacing class with a base unit (e.g., 16px) and named constants (xxs through xxlg). Use EdgeInsets.only or EdgeInsets.symmetric — never EdgeInsets.fromLTRB.
See references/spacing.md for the full AppSpacing class, usage examples, and EdgeInsets preferences.
AppColors with all color constantsAppTextStyle with all text style constantsAppSpacing with spacing scale based on a base unitAppTheme class with light and dark gettersColorScheme, TextTheme, and component themes in each ThemeDataAppTheme.light and AppTheme.dark to MaterialAppAppColorsColorScheme role (or create a theme extension for custom tokens)Theme.of(context).colorScheme.<role> in widgetsColorScheme instances for light and darkTextTheme and component themes (they adapt automatically via colorScheme)MaterialApp via theme and darkThemeBrightness in widget code — let ThemeData handle the switchA widget that branches on brightness has taken over a decision that belongs to ThemeData: every new dark-aware widget repeats the branch, and neither color is reachable from the theme. Delete the branch instead of tidying it. The light value and the dark value become the same ColorScheme role in two themes — declare both in AppColors, assign each to that role exactly as Light and Dark Theme Variants above shows, and pass AppTheme.light and AppTheme.dark to MaterialApp as theme and darkTheme. The widget then drops to a single unconditional read:
@override
Widget build(BuildContext context) {
return ColoredBox(
color: Theme.of(context).colorScheme.surface,
child: child,
);
}
| 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 |