Loading...
Loading...
Structures a Flutter design system as tokens→theme→modifiers→shapes with two-tier tokens (primitives named by measured value, semantic slots named by role) exposed through a ThemeExtension read via an asserting of(context); hand-authors both light and dark ColorScheme instead of ColorScheme.fromSeed, keeps every raw color/hex/Colors./Curves./Duration/BorderRadius/fontSize inside lib/theme/ behind a no-raw-values gate, collapses animation to zero under reduced motion, restores the persisted theme before first paint, bundles fonts (never google_fonts/dynamic_color), and derives each stateful meaning's supporting color last so color is never its sole signal. Use when creating or editing ThemeData/ThemeExtension/ColorScheme, adding or renaming a design token, wiring theme injection or theme-mode persistence, reaching for fromSeed/dynamic_color/google_fonts/FontVariation, structuring a theme/ or DesignSystem/ folder, or reviewing any widget that renders a color, radius, duration, or font.
npx skill4agent add zakariaf/flutter-skills design-system-structurediff lib/theme/Color(0xFF…)references/token-tiers-and-themeextension.mdof()ColorSchemefromSeedlerpreferences/motion-and-reduced-motion.mdNoSplashpumpAndSettlereferences/typography-and-fonts.mdLicenseRegistryFontWeightwghtFontVariationreferences/contrast-and-redundant-encoding.mdMediaQueryscripts/check_raw_values.shscripts/check_font_bundling.shlib/theme/**Color(0x…)Colors.*transparentCurves.*Duration(milliseconds:|seconds:Duration.zeroBorderRadius.circular(n)fontSize: nscripts/check_raw_values.sh// ignoreneutral12grey700darkGreybrandPrimarysurfaceonSurfacehairlineaccentThemeExtensionof(context)constInheritedWidgetof()?? fallbackMaterialAppColorSchemeColorScheme.fromSeeddynamic_colorfromSeedsurfacesurfaceContainerHighColorSchemeThemeDatalerpcopyWithShapeBordert < 0.5lerplerpaccessibility-as-codeMediaQuery.disableAnimationsOf(context)Duration.zerorunAppshared_preferencesnullof()google_fontspubspec.yamlLicenseRegistryFontWeightwghtFontVariation('wght', …)fontFamilyFallbackgoogle_fontsFontVariation('opsz'|'ital', …)paint()BuildContextTheme.of(context).extension<T>()CustomPainterScenepaint()MaterialAppcustom-canvas-and-gesturesMediaQuerydisableAnimationsOfresolveMotionboldTextOfhighContrastOftextScalerMediaQueryaccessibility-as-codetokens → theme → modifiers → shapesThemeExtensionThemeDataColorScheme// lib/theme/primitives.dart — TIER 1. The ONLY file allowed a raw color literal.
// Named by MEASURED value; values are placeholders owned by your design authority.
abstract final class Primitives {
static const neutral08 = Color(0xFF141414); // L .08 — ink
static const neutral96 = Color(0xFFF4F4F4); // L .96 — paper
static const neutral40 = Color(0xFF636363);
static const accent45 = Color(0xFF3A6FF0);
}
// lib/theme/app_colors.dart — TIER 2. Widgets read THESE, never a primitive.
class AppColors extends ThemeExtension<AppColors> {
const AppColors({required this.surface, required this.onSurface, required this.accent});
final Color surface, onSurface, accent;
static AppColors of(BuildContext context) {
final ext = Theme.of(context).extension<AppColors>();
assert(ext != null, 'AppColors missing. Build ThemeData via buildAppTheme().');
return ext!; // assert, never `?? fallback` — a fallback ships an unverified theme.
}
// copyWith + lerp omitted here — see examples/app_theme.dart for the full class.
}cardShapeColorSchemelerpexamples/app_theme.dartColorScheme// WRONG — surfaceContainerHigh, surfaceDim, surfaceBright etc. stay seed-derived;
// your `surface` override does not propagate to them.
ColorScheme.fromSeed(seedColor: Primitives.accent45, surface: c.surface);
// RIGHT — state every role you read. Unstated roles get ColorScheme's defaults,
// not a seed's opinion, and the M3 names mean Material widgets theme themselves.
ColorScheme(
brightness: brightness,
primary: c.accent, onPrimary: c.surface,
surface: c.surface, onSurface: c.onSurface,
surfaceContainerHighest: c.surface, onSurfaceVariant: c.onSurfaceMuted,
outline: c.hairline, error: c.danger, onError: c.surface,
);TextFieldsurfaceContainerHighestonSurfaceVariantoutlineerrorInputDecoration// The one place a widget asks "should I animate?" — collapses under the OS flag.
Duration resolveMotion(BuildContext context, Duration full) =>
MediaQuery.disableAnimationsOf(context) ? Duration.zero : full;
AnimatedContainer(
duration: resolveMotion(context, AppMotion.of(context).medium), // token, not 300ms
curve: AppMotion.of(context).enter, // token, not Curves.easeOut
color: AppColors.of(context).surface,
child: child,
);NoSplash.splashFactoryInkHighlightMaterialAppThemeDatakThemeAnimationDurationthemeAnimationStyle: AnimationStyle.noAnimationreferences/motion-and-reduced-motion.md// bootstrap: settings are loaded BEFORE runApp, so the first frame is correct.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final settings = await Settings.load(); // synchronous by the time runApp runs
final mode = ThemeVariant.tryParse(settings.themeName) // unknown/corrupt -> explicit default
?? ThemeVariant.system;
runApp(App(initialVariant: mode));
}shared_preferencesstate-management-riverpodthemeModeProviderapp-startup-and-bootstrapmain()accessibility-as-codeenum OrderStatus { pending, active, blocked }
extension OrderStatusView on OrderStatus {
IconData get icon => switch (this) { // signal 1: shape differs per status
OrderStatus.pending => Icons.schedule,
OrderStatus.active => Icons.play_arrow,
OrderStatus.blocked => Icons.error_outline,
};
String label(AppLocalizations l) => switch (this) { … }; // signal 2: the word
double get emphasis => this == OrderStatus.blocked ? 2 : 1; // signal 3: monotonic weight
Color color(AppColors c) => switch (this) { // color is DECORATION, derived LAST
OrderStatus.pending => c.onSurfaceMuted,
OrderStatus.active => c.accent,
OrderStatus.blocked => c.danger,
};
}examples/status_encoding.dartColor(0xFF…)Colors.blueGreyCurves.easeOutDuration(milliseconds: 200)BorderRadius.circular(16)fontSize: 14lib/theme/// ignoreAppColorsDimensconstAppColors.of?? AppColors.fallback()ThemeExtensionColorScheme.fromSeed(...)dynamic_colorThemeDatalerpthisDuration.zeroshared_preferencesrunAppimport 'package:google_fonts/…'FontVariation('opsz'|'ital', …)CustomPainterTheme.of(context)paint()node_modulesscripts/check_raw_values.shscripts/check_font_bundling.shlib/lib/theme/**google_fontsThemeExtensionof(context)ColorSchemefromSeedThemeDatacomputeLuminance()references/contrast-and-redundant-encoding.mdcopyWithlerpMediaQuery.disableAnimationsOfnullfontFamilyFallbackTheme.of(context)paint()accessibility-as-codeMediaQuerytextScalercustom-canvas-and-gesturesflutter-performanceconst.selectRepaintBoundarywidget-compositionconststate-management-riverpodthemeModeProviderapp-startup-and-bootstrapmain()runAppi18n-rtl-l10nlint-and-style-configmotion-and-hapticsdesign-review-workflowThemeExtensionThemeDataextensionscolorSchemeColorSchemefromSeedfontFamilyFallbackMediaQueryData.disableAnimationstextScalerColor.computeLuminanceLicenseRegistry