Irbisa · Cheatsheet

Theming & Design Systems

ThemeData, ColorScheme.fromSeed, Material 3, ThemeExtension, custom fonts, dark mode

Middle Developer · 22 questions

  1. 01

    How do you theme a Flutter app with ThemeData, and how does a widget read the theme?

    You pass a ThemeData to MaterialApp(theme: ...), and every widget below reads it with Theme.of(context).

    class App extends StatelessWidget {  const App({super.key});   @override  Widget build(BuildContext context) {    return MaterialApp(…
  2. 02

    What does ColorScheme.fromSeed do, and why is the resulting primary not your exact brand colour?

    fromSeed treats your colour as a seed: it builds tonal palettes from its hue and takes every role (primary, surface, error…) from a fixed tone, so each colour and its on partner are readable by construction.

    const brand = Color(0xFF00695C); final scheme = ColorScheme.fromSeed(seedColor: brand);// scheme.primary != brand: it is the palette tone that reads well on surface // When the literal brand colour is required…
  3. 03

    What is TextTheme, and why use its roles instead of writing a TextStyle on every screen?

    TextTheme is the set of named text styles inside ThemeData: 15 roles from displayLarge to labelSmall.

    // before: one decision copied into every screenText('Monthly total',    style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w600)); // after: a role the theme ownsText('Monthly total', style: Theme.of(context).textTheme.titleLarge);…
  4. 04

    How do you add custom fonts and custom icons to a Flutter app?

    Put the font files in the project, declare them under flutter: fonts: in pubspec.yaml, and use the family name in ThemeData(fontFamily: ...) or a TextStyle.

    # pubspec.yamlflutter:  uses-material-design: true   # the font behind Icons.*   fonts:    - family: Inter…
  5. 05

    What is the difference between Material and Cupertino widgets, and can you use Cupertino widgets inside a MaterialApp?

    Material widgets follow Google's Material Design and are themed by ThemeData; Cupertino widgets copy iOS controls and are themed by CupertinoThemeData.

  6. 06

    How do you support light and dark mode with theme, darkTheme and themeMode?

    Give MaterialApp both a theme and a darkTheme, and let themeMode choose: ThemeMode.system follows the device setting, light and dark force one.

    const _brand = Color(0xFF6750A4); ThemeData buildTheme(Brightness brightness) {  final cs = ColorScheme.fromSeed(seedColor: _brand, brightness: brightness);  return ThemeData(    colorScheme: cs, // brightness comes from the scheme…
  7. 07

    Find the bug: on some phones the white logo is invisible on the app bar even though the app is in its light theme.

    class BrandLogo extends StatelessWidget {  const BrandLogo({super.key});   @override  Widget build(BuildContext context) {    final isDark =        MediaQuery.platformBrightnessOf(context) == Brightness.dark;    return Image.asset(      isDark ? 'assets/logo_white.png' : 'assets/logo_black.png',      height: 24,    );  }}

    MediaQuery.platformBrightnessOf reports the phone's system setting, not the theme the app is drawing.

  8. 08

    This widget builds MaterialApp with a brand theme, yet the box is painted in the default purple. Why?

    class App extends StatelessWidget {  const App({super.key});   @override  Widget build(BuildContext context) {    final primary = Theme.of(context).colorScheme.primary;    return MaterialApp(      theme: ThemeData(        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF00695C)),      ),      home: ColoredBox(color: primary, child: const HomePage()),    );  }}

    Theme.of(context) looks up the tree from the context you pass, and this context belongs to App, which sits above the MaterialApp it builds.

  9. 09

    Find the bug: after switching to dark mode the card turns dark, but the price on it keeps the light theme's colour.

    class _PriceTagState extends State<PriceTag> {  late final TextStyle _priceStyle = Theme.of(context)      .textTheme      .headlineSmall!      .copyWith(color: Theme.of(context).colorScheme.primary);   @override  Widget build(BuildContext context) {    return Card(      child: Padding(        padding: const EdgeInsets.all(16),        child: Text(widget.price, style: _priceStyle),      ),    );  }}

    The style is cached in a late final field, whose initializer runs once, on the first build.

  10. 10

    This section was meant to get red buttons only, but its text lost the app font and a ThemeExtension lookup now returns null. What went wrong?

    @overrideWidget build(BuildContext context) {  return Theme(    data: ThemeData(      colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),    ),    child: const DangerZone(), // buttons, text, BrandTokens.of(context)  );}

    Theme(data: ThemeData(...)) replaces the whole theme for the subtree instead of patching it: the new ThemeData has the default font, default component themes and no extensions.

  11. 11

    _canPay is false, yet this button still paints in full brand colour. Why, and how does a Material button resolve its style?

    const brand = Color(0xFF00695C); FilledButton(  onPressed: _canPay ? _pay : null,  style: const ButtonStyle(    backgroundColor: WidgetStatePropertyAll(brand),  ),  child: const Text('Pay'),);

    WidgetStatePropertyAll(brand) answers brand for every state, disabled included, and a value passed in style beats both the component theme and the button's defaults, so the disabled colour from the defaults never gets a say.

  12. 12

    The design uses Inter at 400, 500, 600 and 700, but on the device several weights look identical. What is wrong with the font setup?

    Flutter can only draw weights you declared as files in pubspec.yaml.

    # pubspec.yaml: every weight the design uses is a declared fileflutter:  fonts:    - family: Inter      fonts:        - asset: assets/fonts/Inter-Regular.ttf    # weight 400 by default…
  13. 13

    What are design tokens, and how do primitive and semantic tokens map onto a Flutter theme?

    Design tokens are the named decisions of a design system (colours, type styles, spacing, radii) kept as data instead of literals.

  14. 14

    What is ThemeExtension, and how do you add custom tokens like a success colour or a spacing value to ThemeData?

    ThemeExtension<T> is the built-in way to carry your own tokens inside ThemeData: you subclass it, register instances in ThemeData.extensions, and read them with Theme.of(context).extension<T>(), so they switch and animate with the theme like the built-in colours.

    @immutableclass BrandTokens extends ThemeExtension<BrandTokens> {  const BrandTokens({required this.success, required this.gutter});   final Color success;  final double gutter;…
  15. 15

    What changes when an app moves from Material 2 to Material 3, and what breaks without anyone noticing?

    Material 3, the default since Flutter 3.16, colours every component from ColorScheme roles instead of primaryColor, separates layers by surface tone instead of shadow, and changes type, shapes and several widgets.

  16. 16

    How does a MaterialApp theme reach Cupertino widgets, and when do you need cupertinoOverrideTheme?

    Every Theme also provides a CupertinoTheme (MaterialBasedCupertinoThemeData) that takes just a few values from ThemeData: brightness, primary and contrasting colours, scaffold background and selection handles.

    final theme = ThemeData(  colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0B6E4F)),  fontFamily: 'Inter',  // Mainly brightness, primaryColor, primaryContrastingColor and  // scaffoldBackgroundColor are derived from the Material theme above.  cupertinoOverrideTheme: const CupertinoThemeData(…
  17. 17

    How do you support Material You dynamic colour on Android while keeping a brand fallback, and when should you not use it?

    Flutter has no built-in wallpaper palette: read it with the dynamic_color package's DynamicColorBuilder, use those schemes when they are non-null, and fall back to ColorScheme.fromSeed(seedColor: brandSeed) everywhere else.

    import 'package:dynamic_color/dynamic_color.dart'; const _brandSeed = Color(0xFF6750A4); class App extends StatelessWidget {  const App({super.key});…
  18. 18

    When should a design system ship its own InheritedWidget instead of putting values in a ThemeExtension, and what does each choice cost?

    Use a ThemeExtension when the value is theme-shaped and should interpolate with the theme; write your own InheritedWidget when it is not: a different lifetime, a different change rate, or values ThemeData has no business carrying.

  19. 19

    How does MaterialApp animate a theme change, and why might switching to dark mode flash white or make a gradient jump?

    MaterialApp wraps the app in an AnimatedTheme that lerps ThemeData over 200 ms (kThemeAnimationDuration), so anything that jumps is either outside that widget or a value ThemeData.lerp does not interpolate.

    MaterialApp(  theme: AcmeTheme.light(),  darkTheme: AcmeTheme.dark(),  themeMode: mode,  // MaterialApp inserts AnimatedTheme; these are its knobs  themeAnimationDuration: const Duration(seconds: 1), // slow it down to debug…
  20. 20

    How would you build one design-system package shared by several apps, and change a token without breaking them?

    The package exports semantic tokens, a ThemeData factory and brand components (never raw constants), and that surface is versioned like any other public API.

    // packages/acme_design/lib/acme_design.dart: the whole public surfaceexport 'src/theme/acme_theme.dart' show AcmeTheme;export 'src/theme/acme_tokens.dart' show AcmeTokens, AcmeSpacing, AcmeContextX;export 'src/components/acme_empty_state.dart' show AcmeEmptyState; // lib/src/theme/acme_theme.dart…
  21. 21

    How do you stop raw colours and off-scale spacing from creeping back into a codebase that already has a design system?

    Make it a CI failure instead of a review comment: a custom lint that flags raw literals outside the design-system package, a committed baseline so the existing count can only fall, and per-theme golden and contrast tests for what a lint cannot see.

    double contrast(Color a, Color b) {  final la = a.computeLuminance();  final lb = b.computeLuminance();  final (hi, lo) = la > lb ? (la, lb) : (lb, la);  return (hi + 0.05) / (lo + 0.05);}…
  22. 22

    You inherit an app with 400 hard-coded colours and text styles and cannot freeze releases. How do you move it onto a design system?

    Strangle it feature by feature behind a compatibility layer: define the tokens first, point every legacy constant at one, then delete the constants a screen at a time while CI ratchets the remaining count down.

    // 1. inventory: grep -rEoh "Color\(0x[0-9A-Fa-f]{8}\)" lib | sort | uniq -c//    412 literals, 31 distinct values; 0xFFF5F5F5 and 0xFFF6F6F6 are one grey // 4. compat layer: nothing renders differently the day it lands,//    and the analyzer now lists every remaining call site@Deprecated('Use context.acme.surfaceMuted; removed in app 4.0')…