Stock Material/Cupertino skills are the wrong default here. If this repo already named colors, type, radius, motion, or a pressable, use those names and values. Do not restyle the app toward FilledButton / NavigationBar / ColorScheme.fromSeed unless the user asked to throw the system away.
Companion skill: flutter-native-ui — only for apps that actually want Material 3 or Cupertino chrome.
References
references/
tokens.md ThemeExtension mount, context accessor, 1:1 rule
pressable.md Scale/sink press, NoSplash, focus ring, semantics
Detect the system first
Before writing a widget, search the repo for:
ThemeExtension<extension <Name> on BuildContextthat returns colors/tokens- files named
*tokens*,*semantics*,*theme*,WIDGETS.md,DESIGN-SYSTEM.md - a widget named
*Pressable NoSplash.splashFactory/splashFactory: NoSplash
Then open a nearby production screen and copy its accessor (context.pt, St6Theme.of(context), context.colors, …). Product code must use that accessor, not a new one.
No system yet, and the user wants one: install the pattern in references/tokens.md and references/pressable.md. One token module, one extension, one pressable. Do not add a third-party UI kit.
Hard rules
Tokens
- Values live in one source (Dart token file, generated from CSS/JSON, or a signed-off
tokens.dart). Widgets never contain raw hex, magic radii, or ad-hoc durations. - Runtime mount is a
ThemeExtension<T>onThemeData.extensions, plus a single context accessor. Do not import aFooColorsclass into random widgets beside the extension. - 1:1 with the system file. If the handoff says
#C9A24B, the token is#C9A24B. Do not “improve” contrast, invent a light theme, or add roles the system does not name. - Missing role: add it to the token source and the extension together, or ask. Never
#hexin the widget as a shortcut. - Dark mode only exists if the system shipped it. Do not invent a dark palette.
Chrome
- A stranger should not be able to tell the app is default Flutter Material. No ink ripples, no M3 primary-container pills, no stock
NavigationBarindicator, unless the system specifies them. - Every tap goes through the house
Pressable(create it if the repo has none). Press = scale and/or sink.splashFactory: NoSplash.splashFactoryand transparent overlays on the theme and on the pressable. - Do not wrap branded controls in
FilledButton/ElevatedButton/InkWell“to get accessibility for free” and then fight the splash. Put semantics on the pressable. - Use Material behavior (focus, media query, text scaler,
Shortcuts) without Material look.
Type and space
- Type roles come from the system (or the extension’s text styles), not
TextTheme.of(context).titleLargeunless the system mapped ontoTextTheme. - Spacing, radius, and motion come from token numbers (
space200,radiusControl,durationPress). NoEdgeInsets.all(13)and noDuration(milliseconds: 180)invented at the call site.
Keys and debug
- Interactive controls:
const ValueKey<String>('kebab-name')so tests canfind.byKey. - God-mode, galleries, prefills, jump chips:
kDebugModeonly, and they still go through the real auth/session path. No release backdoors.
Do not
- Add
google_fontsif the system already vendors faces. If goldens matter, load those faces withFontLoader— Roboto goldens are not the product. - Add
flutter_screenutil, GetX, or a second design package. - Mix stock M3 components onto a branded surface “just this once.”
Build a screen
- Read the token file, the extension, the pressable, and one existing screen.
- Scaffold with the project’s shell (custom app bar / tab bar), not a fresh
AppBar+NavigationBar. - Color, type, space, radius, motion: extension/tokens only.
- Taps: house pressable. Icons: the system’s icon set, not
Icons.favoriteunless that is the system. - Safe area and keyboard:
SafeAreaon content,MediaQuery.sizeOf/viewInsetsOf/textScalerOf. Window width breakpoints if the system defines them; otherwise keep the existing shell’s rule. - Verify with the project’s analyzer and a running frame.
When to refuse Material defaults
Refuse these on a branded surface unless the system’s widget inventory lists them:
FilledButton, NavigationBar, NavigationRail, NavigationDrawer, SnackBar (if the system has a toast), FloatingActionButton, Chip, Card, Material with elevation tint, InkWell with default splash.
OS-native affordances that are not chrome may still use .adaptive() (Switch.adaptive, Checkbox.adaptive) when the system does not specify a replacement.
Verify
grepthe files you touched forColor(0x/Color.from/#. Hits belong in the token file, not the widget.- Every new tap target goes through the house pressable.
- Analyzer clean. Hot reload. Confirm the running frame against a nearby branded screen, not against Material 3 gallery memory.
- Widget tests use
find.byKey, not visible copy, when a key exists.