theming
Skill0 starsChanged 50 days agoMITView on GitHub
Covers how to add or change a design token — extending AppColors/AppSpacing/AppTypography, adding a component sub-theme to AppTheme instead of inline styling, swapping the seed brand color, adding a custom font, and when a widget belongs in common_widgets vs a feature's own widgets folder; triggers whenever the visual design system itself is being changed, not just consumed.
3 other projects use a version of this.
Adopted by
Most recently taken up by
- KyaniteLabs/tastechecktheir own version, first seen Nov 2, 2026
- nimadorostkar/Claude-Skills-collectiontheir own version, first seen Oct 13, 2026
- pierrecomputer/pierretheir own version, first seen Oct 8, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- pierrecomputer/pierre★ 6.5ksame name
- nimadorostkar/Claude-Skills-collection★ 33same name
- KyaniteLabs/tastecheck★ 12same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository35 other files it gives its agents.
- AGENTS.mdAGENTS.md
- bootstrap-projectSkill
- choose-data-stackSkill
- code-reviewSkill
- conventional-commitsSkill
- data-providerSkill
- domain-modelingSkill
- error-handlingSkill
- flavors-and-flagsSkill
- flutter-fvmSkill
- localizationSkill
- new-featureSkill
- quality-gateSkill
- routingSkill
- state-managementSkill
- testingSkill
- ui-screenSkill
- bootstrap-projectSkill
- choose-data-stackSkill
- code-reviewSkill
- conventional-commitsSkill
- data-providerSkill
- domain-modelingSkill
- error-handlingSkill
- flavors-and-flagsSkill
- flutter-fvmSkill
- flutter_fvmSkill
- localizationSkill
- new-featureSkill
- quality-gateSkill
- routingSkill
- state-managementSkill
- testingSkill
- themingSkill
- ui-screenSkill
The file
We split this skill into the rules it gives an agent
7 rules. 0 are common, 0 are distinctive, and 7 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
0
Distinctive
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 16
Add a new semantic color only when it means something Material's ColorScheme roles don't already cover (primary, secondary, error, surface, …
- Very specific: line 38
Never reference AppColors.success/warning/etc.
- Very specific: line 44
Add a new spaceN value only if it doesn't already sit on the 4pt scale, and prefer adding a new semantic alias for a recurring layout need …
- Very specific: line 73
Add an override here — not a one-off TextStyle at a call site — when a specific text role needs a consistent treatment app-wide (e.g.
- Very specific: line 88
Don't add a color: to a style here.
- Very specific: line 93
If you find yourself passing the same style:/shape:/padding: to every instance of a widget type across the app, that belongs in AppTheme as …
- Very specific: line 168
Add the font dependency — either google_fonts (fvm flutter pub add google_fonts from packages/ui/) for a hosted font, or a font asset …
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Theming and design tokens
packages/ui/lib/src/theme/ holds every design token: AppColors,
AppSpacing, AppTypography, AppTheme, and the BuildContextX
extension that exposes them to widgets. This skill is about changing
the design system itself — for consuming existing tokens in a screen, see
.agents/skills/ui-screen/SKILL.md section 3.
1. Extending AppColors
Add a new semantic color only when it means something Material's
ColorScheme roles don't already cover (primary, secondary,
error, surface, ...). Check context.theme.colorScheme first.
abstract final class AppColors {
static const Color seed = Color(0xFF6750A4);
static const ColorScheme lightScheme = ColorScheme.light(primary: seed);
static final ColorScheme darkScheme = ColorScheme.fromSeed(
seedColor: seed,
brightness: Brightness.dark,
);
static const Color success = Color(0xFF2E7D32);
static const Color warning = Color(0xFFED6C02);
// New semantic role — added only because neither `error` nor any other
// ColorScheme role fits "a value the user should double-check".
static const Color caution = Color(0xFFF9A825);
}Never reference AppColors.success/warning/etc. directly for something
that already has a colorScheme equivalent — e.g. don't add a danger
alias for colorScheme.error.
2. Extending AppSpacing
Add a new spaceN value only if it doesn't already sit on the 4pt scale,
and prefer adding a new semantic alias for a recurring layout need
over reaching for a raw spaceN constant at every call site:
abstract final class AppSpacing {
static const double space1 = 4;
static const double space2 = 8;
// ...
/// Gap between a list item's leading icon and its label — recurring
/// enough across features to deserve its own name instead of every
/// call site picking space2 by convention.
static const double iconLabelGap = space2;
}3. Extending AppTypography
AppTypography.textTheme(base) takes Flutter's default TextTheme and
returns the app's. The template ships it as a pass-through (no custom
font, kept dependency-light):
abstract final class AppTypography {
static TextTheme textTheme(TextTheme base) => base;
}Add an override here — not a one-off TextStyle at a call site — when a
specific text role needs a consistent treatment app-wide (e.g. every
titleLarge should be slightly heavier):
abstract final class AppTypography {
static TextTheme textTheme(TextTheme base) => base.copyWith(
titleLarge: base.titleLarge?.copyWith(fontWeight: FontWeight.w600),
);
}Colors stay out of AppTypography deliberately — ThemeData applies
colorScheme.onSurface (and friends) to text automatically, so a widget
reads context.theme.textTheme.titleMedium without a color baked in.
Don't add a color: to a style here; that would fight the automatic
on-color mapping and break dark mode.
4. Adding a component sub-theme instead of inline styling
If you find yourself passing the same style:/shape:/padding: to
every instance of a widget type across the app, that belongs in
AppTheme as a component sub-theme, not repeated at each call site:
// Wrong — repeated at every ElevatedButton call site across the app
ElevatedButton(
style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppSpacing.radiusMd),
)),
onPressed: onPressed,
child: child,
);
// Right — set once in AppTheme, every ElevatedButton in the app picks it up
abstract final class AppTheme {
static ThemeData get light => _themeFrom(AppColors.lightScheme);
static ThemeData get dark => _themeFrom(AppColors.darkScheme);
static ThemeData _themeFrom(ColorScheme colorScheme) {
final base = ThemeData(colorScheme: colorScheme, useMaterial3: true);
return base.copyWith(
textTheme: AppTypography.textTheme(base.textTheme),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppSpacing.radiusMd),
),
),
),
cardTheme: CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppSpacing.radiusLg),
side: BorderSide(color: colorScheme.outlineVariant),
),
),
);
}
}A widget then just writes ElevatedButton(onPressed: ..., child: ...) or
Card(child: ...) with zero styling arguments, and every instance in the
app stays visually consistent by construction — a design change becomes a
one-file edit instead of a find-and-replace across every screen.
5. Swapping the brand seed color
AppColors.seed drives both lightScheme and darkScheme via
ColorScheme.fromSeed (light mode is expressed via the simpler
ColorScheme.light(primary: seed) constructor here, but both ultimately
derive from the same seed value). Replacing the neutral template seed
with a real brand color is a one-line change that regenerates a
harmonious light and dark palette without hand-tuning either:
abstract final class AppColors {
// Was: static const Color seed = Color(0xFF6750A4);
static const Color seed = Color(0xFF0057B8); // brand primary
// lightScheme/darkScheme below are unchanged — both re-derive from seed.
};AppTheme.light/AppTheme.dark and every widget that reads
context.theme.colorScheme.primary update automatically; there's no
second place to edit. Verify the swap with the existing theme test's
expectations in mind (packages/ui/test/theme/app_theme_test.dart checks
AppTheme.light.colorScheme.primary == AppColors.lightScheme.primary)
and check contrast for any hardcoded on-primary text.
6. Adding a custom font
The template ships no custom typeface by design. To add one:
- Add the font dependency — either
google_fonts(fvm flutter pub add google_fontsfrompackages/ui/) for a hosted font, or a font asset declared underflutter: fonts:inpackages/ui/pubspec.yamlfor a licensed/custom file. Replace
AppTypography.textTheme's pass-through with the font applied to the baseTextTheme:// With google_fonts: abstract final class AppTypography { static TextTheme textTheme(TextTheme base) => GoogleFonts.interTextTheme(base); }// With a bundled font asset (pubspec.yaml `flutter: fonts:` entry // named "Inter"): abstract final class AppTypography { static TextTheme textTheme(TextTheme base) => base.apply(fontFamily: 'Inter'); }AppTheme._themeFromalready callsAppTypography.textTheme(base.textTheme)— no further wiring needed; both light and dark themes pick it up.
7. common_widgets/ vs a feature's own widgets/ folder
A new widget starts in its feature's own
features/<feature>/presentation/widgets/ folder. It is promoted to
packages/ui/lib/src/common_widgets/ only once a second, different
feature genuinely needs the same widget — not pre-emptively, and not just
because it "feels generic" (YAGNI, per packages/ui/AGENTS.md and the
common_widgets.dart barrel's doc comment). Before writing a new widget
at all, check whether a plain Material widget already covers the need.
// First use: features/tasks/presentation/widgets/task_card.dart
// A second feature later needs the identical card (e.g. features/archive/):
// now — and only now — move it to:
// packages/ui/lib/src/common_widgets/task_card.dart
// and export it from common_widgets.dart.Common mistakes
- Adding a raw
Color(0xFF...)or magic-number spacing value instead of a token, even "just this once". - Styling a widget type inline at every call site instead of adding an
AppThemesub-theme. - Baking a color into an
AppTypographytext style, breaking automatic dark-mode on-color mapping. - Promoting a widget to
common_widgets/on its first use, "just in case" a second feature needs it later. - Forgetting that
AppColors.lightSchemeanddarkSchemeare built two different ways (ColorScheme.lightvsColorScheme.fromSeed) — both must be checked after any seed-related change.
Checklist
- New color/spacing/typography value added to the matching token class, not inlined at the call site.
- A widget-type-wide style goes into
AppThemeas a sub-theme, not repeated per call site. - Any seed-color change verified against both
AppColors.lightSchemeanddarkScheme. - A new widget stays in its feature's
widgets/folder until a second feature needs it; only then does it move tocommon_widgets/(with the barrel updated). packages/ui/test/theme/app_theme_test.dart-style coverage still passes after a token change.
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
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.

