Skip to content
Mobile
Skill

/material-theming

Best practices for Flutter theming with Material 3, treating ThemeData as the single source of truth for colors, typography, component styles, and spacing. Use when creating, modifying, or reviewing ThemeData, ColorScheme, TextTheme, component themes, spacing systems, or

BOOST
From plugin
vgv-ai-flutter-plugin
17315 skills1 agent3 hooks2 MCP
Install
$ npx -y skills add VeryGoodOpenSource/vgv-ai-flutter-plugin --skill material-theming --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/material-theming

Context preview

The summary Claude sees to decide when to auto-load this skill.

Best practices for Flutter theming with Material 3, treating ThemeData as the single source of truth for colors, typography, component styles, and spacing. Use when creating, modifying, or reviewing ThemeData, ColorScheme, TextTheme, component themes, spacing systems, or

SKILL.md

material-theming.SKILL.md
name: material-theming
description: >
  Best practices for Flutter theming with Material 3, treating ThemeData as the single source
  of truth for colors, typography, component styles, and spacing. Use when creating,
  modifying, or reviewing ThemeData, ColorScheme, TextTheme, component themes, spacing
  systems, or light/dark mode support, and 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

Material 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 `ThemeData` as the single source of truth** — never inline colors or text styles in widgets
  • **Reference colors via `Theme.of(context).colorScheme`** — never `Colors.blue`, `Colors.red`, or any hardcoded `Color` values
  • **Reference text styles via `Theme.of(context).textTheme`** — never inline `TextStyle(...)` in widget code. `fontSize` and `fontWeight` never appear inside a `build` method
  • **Use `ColorScheme` for all color definitions** — Material 3's structured color system
  • **Centralize component themes in `ThemeData`** — 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 count
  • **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 `ThemeData` so theme switching requires zero conditional logic in widgets
  • **Never branch on brightness or theme mode in widget code** — no `MediaQuery.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 formatting
  • **Prefer `EdgeInsets.only` and `EdgeInsets.symmetric`** — never `EdgeInsets.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

Every color reaches widgets through a `ColorScheme` role. Define two schemes — one light, one dark — inside a single `AppTheme` class, and pass both to `MaterialApp` as `theme` and `darkTheme`. That pair is what makes brightness branching in widget code unnecessary:

class AppTheme {
  static ThemeData get light => ThemeData(
    colorScheme: const ColorScheme.light(
      brightness: Brightness.light,
      surface: AppColors.surfaceColor,
      // ... remaining color roles
    ),
  );

  static ThemeData get dark => ThemeData(
    colorScheme: const ColorScheme.dark(
      brightness: Brightness.dark,
      surface: AppColors.surfaceColorDark,
      // ... remaining color roles
    ),
  );
}

See [references/color-scheme.md](references/color-scheme.md) for the full 45-role `ColorScheme` configuration, `ColorScheme.fromSeed`, and how widgets read colors.

Typography

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](references/typography.md) for font asset setup, the full `AppTextStyle` class, `TextTheme` integration, and widget access patterns.

Replacing a Hardcoded `TextStyle`

Map 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.

Component Themes

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](references/components.md) for FilledButton, InputDecoration, and AppBar theme examples, the complete theme assembly, and widget access patterns.

De-duplicating Repeated Widget Styling

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

Read more
Ships withvgv-ai-flutter-plugin

A Claude Code plugin that accelerates Flutter & Dart development with best-practices skills from Very Good Ventures. Developed with 💙 by Very Good Ventures 🦄

Get the whole plugin

Other skills on vgv-ai-flutter-plugin.