agentleFS
gabuldevgabuldev/flutter-skills/skills/flutter-theming/SKILL.md

flutter-theming

Skill12 starsChanged 55 days agoMITView on GitHub

Use when creating, migrating or auditing a Material 3 theme in a Flutter app - assembling ColorScheme and ThemeData, theming a specific component, replacing a hardcoded colour with a theme token, or reviewing visual consistency. Triggers on any mention of colour, theme, dark mode, or "this looks off-brand" on a Flutter screen.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

18 rules. About 0 are common, 1 is distinctive, and 17 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 17

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Always prefer importing a token from the design system over duplicating the value in an app.

    line 8
  2. Very specific:

    Enable Material 3: Add useMaterial3: true to ThemeData

    line 217
  3. Very specific:

    Read every app's root widget (app_widget.dart / main.dart)

    line 333
  4. Very specific:

    Read all files under */lib/theme/, */lib/shared/theme/

    line 334
  5. Very specific:

    Search for hardcoded Color(0x...) values in widget files (outside theme files)

    line 335
  6. Very specific:

    Search for deprecated APIs: accentColor, primarySwatch, FlatButton, RaisedButton, MaterialState

    line 336
  7. Very specific:

    Check that all apps import from design_system instead of duplicating token values

    line 337
  8. Very specific:

    Verify useMaterial3: true is set in all apps

    line 338
  9. Very specific:

    Always read the app's existing app_widget.dart and theme files before making changes

    line 367
  10. Very specific:

    Import shared tokens from design_system — never duplicate DSColors, DSSpacing, DSRadius, or DSTypography values

    line 368
  11. Very specific:

    Use abstract final class for all token/color/style classes (cannot be instantiated)

    line 369
  12. Very specific:

    All token values must be static const

    line 370
  13. Very specific:

    Use ColorScheme.fromSeed() — never construct ColorScheme() manually unless overriding specific slots

    line 371
  14. Very specific:

    Use *ThemeData suffix for component themes (CardThemeData, AppBarThemeData, etc.)

    line 372
  15. Very specific:

    Use WidgetStateProperty and WidgetState — never MaterialStateProperty or MaterialState

    line 373
  16. Very specific:

    Use .withValues(alpha: 0.5) — never .withOpacity(0.5) (deprecated)

    line 374
  17. Very specific:

    Prefer FilledButton for primary actions over ElevatedButton (Material 3 convention)

    line 375
  18. Very specific:

    When the app already has an app-local <App>DesignTokens-style class, migrate its values into the design_system package or into …

    line 377

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

You are working on theming for a Flutter app. The patterns below assume a shared design_system/ package holding cross-app tokens (DSColors, DSSpacing, DSRadius, DSTypography), with each app free to keep its own lib/theme/ for app-specific overrides.

Always prefer importing a token from the design system over duplicating the value in an app.


Workflow: create

Scaffold a complete theme for the target app, taken from the request or the skill argument. If it is not clear which app, ask before writing anything: each one has its own theme directory and overwriting the wrong app is silent.

Theme Directory Structure

<app>/lib/shared/theme/
├── app_theme.dart           ← ThemeData factory (light/dark)
├── app_colors.dart          ← App-specific color overrides (imports DSColors)
└── app_text_styles.dart     ← App-specific text style overrides (imports DSTypography)

app_colors.dart

import 'package:flutter/material.dart';
import 'package:design_system/design_system.dart';

/// App-specific color aliases. Delegates to DSColors for shared tokens
/// and defines overrides only when this app diverges from the design system.
abstract final class AppColors {
  // Re-export shared tokens for convenience
  static const Color primary = DSColors.brandPrimary;
  static const Color secondary = DSColors.brandSecondary;
  static const Color error = DSColors.error;
  static const Color success = DSColors.success;
  static const Color warning = DSColors.warning;

  // App-specific overrides
  static const Color scaffoldBackground = Color(0xFFF8F9FA);
  static const Color cardBackground = DSColors.neutral0;
  static const Color divider = DSColors.neutral200;
}

app_text_styles.dart

import 'package:flutter/material.dart';
import 'package:design_system/design_system.dart';

/// App-specific text style overrides. Use DSTypography directly when possible.
abstract final class AppTextStyles {
  static TextStyle get pageTitle => DSTypography.headingH4;
  static TextStyle get sectionTitle => DSTypography.headingH5;
  static TextStyle get body => DSTypography.body14Regular;
  static TextStyle get caption => DSTypography.caption;

  // App-specific additions only
  static TextStyle get price => DSTypography.price;
}

app_theme.dart (Material 3)

import 'package:flutter/material.dart';
import 'package:design_system/design_system.dart';
import 'app_colors.dart';

class AppTheme {
  static ThemeData light() {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: AppColors.primary,
      brightness: Brightness.light,
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,
      fontFamily: 'Roboto',
      scaffoldBackgroundColor: AppColors.scaffoldBackground,

      // --- Component Themes (use *ThemeData suffix) ---
      appBarTheme: AppBarThemeData(
        backgroundColor: colorScheme.surface,
        foregroundColor: colorScheme.onSurface,
        elevation: 0,
        centerTitle: false,
      ),
      cardTheme: CardThemeData(
        elevation: 0,
        shape: RoundedRectangleBorder(
          borderRadius: DSRadius.lgBorderRadius,
          side: BorderSide(color: AppColors.divider),
        ),
        color: AppColors.cardBackground,
      ),
      filledButtonTheme: FilledButtonThemeData(
        style: FilledButton.styleFrom(
          backgroundColor: AppColors.primary,
          foregroundColor: DSColors.neutral0,
          shape: RoundedRectangleBorder(
            borderRadius: DSRadius.mdBorderRadius,
          ),
          minimumSize: const Size(double.infinity, 48),
          textStyle: const TextStyle(
            fontWeight: FontWeight.w600,
            fontSize: 14,
          ),
        ),
      ),
      elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
          backgroundColor: AppColors.primary,
          foregroundColor: DSColors.neutral0,
          shape: RoundedRectangleBorder(
            borderRadius: DSRadius.mdBorderRadius,
          ),
          minimumSize: const Size(double.infinity, 48),
        ),
      ),
      outlinedButtonTheme: OutlinedButtonThemeData(
        style: OutlinedButton.styleFrom(
          foregroundColor: AppColors.primary,
          side: BorderSide(color: AppColors.primary),
          shape: RoundedRectangleBorder(
            borderRadius: DSRadius.mdBorderRadius,
          ),
          minimumSize: const Size(double.infinity, 48),
        ),
      ),
      textButtonTheme: TextButtonThemeData(
        style: TextButton.styleFrom(
          foregroundColor: AppColors.primary,
        ),
      ),
      inputDecorationTheme: InputDecorationTheme(
        border: OutlineInputBorder(
          borderRadius: DSRadius.mdBorderRadius,
        ),
        contentPadding: const EdgeInsets.symmetric(
          horizontal: DSSpacing.lg,
          vertical: DSSpacing.md,
        ),
      ),
      navigationBarTheme: NavigationBarThemeData(
        indicatorColor: colorScheme.secondaryContainer,
        labelTextStyle: WidgetStateProperty.resolveWith((states) {
          if (states.contains(WidgetState.selected)) {
            return DSTypography.label.copyWith(color: AppColors.primary);
          }
          return DSTypography.label;
        }),
      ),
      dividerTheme: DividerThemeData(
        color: AppColors.divider,
        thickness: 1,
        space: 0,
      ),

      // --- Typography ---
      textTheme: const TextTheme(
        displayLarge: DSTypography.headingH4,
        headlineMedium: DSTypography.headingH5,
        titleLarge: DSTypography.headingH6,
        bodyLarge: DSTypography.body16Regular,
        bodyMedium: DSTypography.body14Regular,
        bodySmall: DSTypography.body12Regular,
        labelSmall: DSTypography.label,
      ),
    );
  }

  static ThemeData dark() {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: AppColors.primary,
      brightness: Brightness.dark,
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,
      fontFamily: 'Roboto',
      // Mirror the light theme structure but with dark-appropriate values.
      // Override specific component themes as needed.
    );
  }
}

Applying the Theme

In app_widget.dart:

MaterialApp(
  theme: AppTheme.light(),
  darkTheme: AppTheme.dark(),
  themeMode: ThemeMode.system,
  // ...
)

Workflow: migrate

Migrate an existing app to Material 3 theming. Read the app's current app_widget.dart and any existing theme files first.

Migration Checklist

  1. Enable Material 3: Add useMaterial3: true to ThemeData
  2. Replace primarySwatch with colorScheme: ColorScheme.fromSeed(seedColor: ...)
  3. Apply deprecated replacements:
DeprecatedReplacement
accentColorcolorScheme.secondary
primaryColor (in ThemeData)colorScheme.primary
AppBarTheme.colorAppBarThemeData.backgroundColor
AppBarTheme.brightnessAppBarThemeData.systemOverlayStyle
BottomNavigationBarNavigationBar
FlatButtonTextButton
RaisedButtonElevatedButton
ButtonThemeTextButtonThemeData / ElevatedButtonThemeData / OutlinedButtonThemeData
MaterialStatePropertyWidgetStateProperty
MaterialStateWidgetState
  1. Normalize component theme class names (use *ThemeData suffix):
Old NameCorrect Name
CardTheme(...) constructor in ThemeDataCardThemeData(...)
AppBarTheme(...)AppBarThemeData(...)
IconTheme(...) in ThemeDataIconThemeData(...)
TabBarTheme(...)TabBarThemeData(...)
DialogTheme(...)DialogThemeData(...)
BottomSheetTheme(...)BottomSheetThemeData(...)
TooltipTheme(...)TooltipThemeData(...)
  1. Replace hardcoded colors with DSColors or Theme.of(context).colorScheme references
  2. Update typography to Material 3 scale:
Old (Material 2)New (Material 3)
headline1displayLarge
headline2displayMedium
headline3displaySmall
headline4headlineMedium
headline5headlineSmall
headline6titleLarge
subtitle1titleMedium
subtitle2titleSmall
bodyText1bodyLarge
bodyText2bodyMedium
captionbodySmall
buttonlabelLarge
overlinelabelSmall

Button Styling After Migration

// ElevatedButton with full styling
ElevatedButton(
  style: ElevatedButton.styleFrom(
    backgroundColor: AppColors.primary,
    foregroundColor: Colors.white,
    disabledBackgroundColor: DSColors.neutral200,
    shape: RoundedRectangleBorder(
      borderRadius: DSRadius.mdBorderRadius,
    ),
    padding: const EdgeInsets.symmetric(
      horizontal: DSSpacing.xl,
      vertical: DSSpacing.md,
    ),
  ),
  onPressed: () {},
  child: const Text('Action'),
)

// FilledButton (Material 3 preferred primary action)
FilledButton(
  onPressed: () {},
  child: const Text('Primary Action'),
)

// FilledButton.tonal (secondary emphasis)
FilledButton.tonal(
  onPressed: () {},
  child: const Text('Secondary Action'),
)

State-Dependent Styling

// Use WidgetStateProperty (NOT MaterialStateProperty) for state-dependent styles
ButtonStyle(
  backgroundColor: WidgetStateProperty.resolveWith((states) {
    if (states.contains(WidgetState.disabled)) return DSColors.neutral200;
    if (states.contains(WidgetState.pressed)) return AppColors.primary.withValues(alpha: 0.8);
    if (states.contains(WidgetState.hovered)) return AppColors.primary.withValues(alpha: 0.9);
    return AppColors.primary;
  }),
  foregroundColor: WidgetStateProperty.resolveWith((states) {
    if (states.contains(WidgetState.disabled)) return DSColors.neutral500;
    return DSColors.neutral0;
  }),
  overlayColor: WidgetStateProperty.resolveWith((states) {
    if (states.contains(WidgetState.pressed)) return Colors.white.withValues(alpha: 0.12);
    if (states.contains(WidgetState.hovered)) return Colors.white.withValues(alpha: 0.08);
    return null;
  }),
)

// Shorthand for simple cases
WidgetStateProperty.all(DSColors.neutral0)

Workflow: audit

Audit theme consistency across apps and report issues.

Audit Steps

  1. Read every app's root widget (app_widget.dart / main.dart)
  2. Read all files under */lib/theme/, */lib/shared/theme/
  3. Search for hardcoded Color(0x...) values in widget files (outside theme files)
  4. Search for deprecated APIs: accentColor, primarySwatch, FlatButton, RaisedButton, MaterialState
  5. Check that all apps import from design_system instead of duplicating token values
  6. Verify useMaterial3: true is set in all apps

Report Format

Produce a markdown report with:

## Theme Audit

### Material 3 Status
- <app>: [enabled/disabled]   (one line per app)

### Deprecated API Usage
- [file:line] — description of deprecated usage and fix

### Hardcoded Colors (should use DSColors/AppColors)
- [file:line] — Color(0xFF...) → suggested token

### Token Duplication
- [file] duplicates [token] already in design_system

### Recommendations
1. ...

Instructions

  1. Always read the app's existing app_widget.dart and theme files before making changes
  2. Import shared tokens from design_system — never duplicate DSColors, DSSpacing, DSRadius, or DSTypography values
  3. Use abstract final class for all token/color/style classes (cannot be instantiated)
  4. All token values must be static const
  5. Use ColorScheme.fromSeed() — never construct ColorScheme() manually unless overriding specific slots
  6. Use *ThemeData suffix for component themes (CardThemeData, AppBarThemeData, etc.)
  7. Use WidgetStateProperty and WidgetState — never MaterialStateProperty or MaterialState
  8. Use .withValues(alpha: 0.5) — never .withOpacity(0.5) (deprecated)
  9. Prefer FilledButton for primary actions over ElevatedButton (Material 3 convention)
  10. Always set useMaterial3: true
  11. When the app already has an app-local <App>DesignTokens-style class, migrate its values into the design_system package or into app_colors.dart / app_text_styles.dart — do not leave duplicate token files

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.