← Back to blog
Flutter Theming Foundations · Part 6 of 6
August 29, 202628 min read

100 Questions to Master Flutter Theming Foundations

FlutterDartTheming

100 Questions to Master Flutter Theming Foundations

This is Part 6, the finale of the Flutter Theming Foundations series. It's a self-test: 100 questions grouped by the five content parts, each with a Hint and a Solution, followed by 10 coding mini-exercises and a capstone.

How to use this bank

  • Try cold first. Read the question, answer in your head or in DartPad, then open the hint.
  • Hint before solution. The hint points you at the right part; the solution is the full answer.
  • Run the coding ones. Paste them into DartPad or a scratch flutter create app and actually see them render in light and dark.
  • Pace yourself. A section a day beats a cram. Mark the ones you missed and revisit.

Sections: 1) Why themes (Q1–20) · 2) ThemeData (Q21–40) · 3) ColorScheme (Q41–60) · 4) TextTheme (Q61–80) · 5) Light & dark (Q81–100).


Section 1 — Why a theme system (Q1–20)

Q1. [Basic] (Theory) In one sentence, what is a theme system?

Hint

Think "single source of truth" — see Part 1.

Solution

A single, central source of truth for an app's visual style (colors, text, component defaults) that widgets reference instead of hardcoding, so a change happens in one place and propagates everywhere.

Q2. [Basic] (Theory) Define "technical debt" in the context of hardcoded styling.

Hint

It's a future cost that charges interest.

Solution

The future cost incurred by hardcoding values now: every later style change costs more than it should (you must find and edit each copy), and the "interest" compounds as the app grows.

Q3. [Basic] (Theory) What does it mean that a themed widget "asks for" a color rather than "asserts" one?

Hint

Compare TextStyle(color: Colors.black) with ...colorScheme.onSurface.

Solution

Instead of declaring an absolute value (Colors.black), the widget reads a role from the theme (Theme.of(context).colorScheme.onSurface), getting a context-appropriate answer (e.g. light vs dark) it doesn't have to know about.

Q4. [Basic] (Theory) Which single feature most clearly exposes the cost of hardcoded colors?

Hint

It falsifies "the background is white" everywhere at once.

Solution

Dark mode. Every hardcoded color bakes in a background assumption, so adding dark mode becomes an audit of every widget unless colors were themed.

Q5. [Basic] (Theory) Give the spreadsheet analogy for hardcoded vs themed values.

Hint

Typing a number vs typing =Prices!B2.

Solution

Hardcoding is typing a literal number into a cell; theming is typing a reference (=Prices!B2). Change the one source cell and every reference updates.

Q6. [Medium] (Theory) Why is a shared const brandPurple = Color(...) still inferior to a theme?

Hint

It fixes duplication but not context.

Solution

It's still one absolute value: it can't be one color in light mode and another in dark, can't vary per brand, and carries no semantic role. A theme provides context-sensitive and role-named values.

Q7. [Medium] (Theory) Name two costs of hardcoding unrelated to a color changing.

Hint

Think consistency and accessibility.

Solution

Consistency (you accumulate several near-identical greys nobody chose deliberately) and accessibility (contrast ratios become accidental). Also: slower onboarding, code/design language mismatch.

Q8. [Medium] (Theory) Why is "I'll add the theme later" usually a bad trade?

Hint

Compare the cost of starting vs retrofitting.

Solution

Retrofitting cost scales with app size (more screens = more literals to migrate), while starting themed costs ~nothing because flutter create already ships a ThemeData. Cheapest moment is the first color you type.

Q9. [Medium] (Coding) Rewrite Text('Hi', style: TextStyle(color: Colors.black)) to ask the theme.

Hint

Use colorScheme.onSurface.

Solution
Text('Hi', style: TextStyle(color: Theme.of(context).colorScheme.onSurface));

Q10. [Medium] (Coding) Convert AppBar(backgroundColor: Color(0xFF6750A4)) so the color comes from the theme.

Hint

colorScheme.primary (or set appBarTheme globally).

Solution
AppBar(backgroundColor: Theme.of(context).colorScheme.primary);

Better yet, set appBarTheme once on ThemeData so every AppBar is consistent.

Q11. [Basic] (Theory) What does Theme.of(context) return?

Hint

The nearest one of these objects.

Solution

The nearest ThemeData found by walking up the widget tree from context.

Q12. [Medium] (Theory) Why does grep-replacing a hex color across a codebase tend to miss instances?

Hint

Same color, many spellings.

Solution

The same color appears in different forms — uppercase/lowercase hex, Color.fromARGB(...), a near-equivalent Colors.deepPurple — so a single literal search won't catch them all.

Q13. [Medium] (Theory) How does a theme help with design handoff?

Hint

Both sides speak the same language.

Solution

Designers describe UI in tokens/roles (primary, surface, title). When code uses the same named roles, handoff and review become "use primary" instead of translating to and arguing about hex codes.

Q14. [Advanced] (Theory) Why is "whitelabeling" nearly impossible with hardcoded colors but easy with a theme?

Hint

One app, several brands.

Solution

Hardcoded, each brand needs its colors changed in dozens of places → divergent forks. Themed, each brand is one ThemeData/seed selected by config, with widgets unchanged because they reference roles.

Q15. [Basic] (Theory) What's the cheapest moment to adopt a theme, per Part 1?

Hint

The first time you do a certain thing.

Solution

The first time you type a color. The infrastructure (ThemeData) already exists from flutter create.

Q16. [Medium] (Coding) A screen uses Colors.grey, Colors.grey[700], and Colors.black54 for "secondary text." Replace them with one themed role.

Hint

There's a low-emphasis on-surface role.

Solution
final c = Theme.of(context).colorScheme.onSurfaceVariant;
// use c for all three

One role replaces the ad-hoc greys (covered in Part 3).

Q17. [Advanced] (Theory) Explain how the inherited-widget nature of the theme makes runtime restyle automatic.

Hint

Reading subscribes; changing notifies.

Solution

Theme.of(context) subscribes the widget to the inherited Theme. Swapping the ThemeData notifies all dependents, which rebuild and re-read the new values — no manual listeners on leaf widgets.

Q18. [Medium] (Theory) Why does using semantic roles reduce PR review friction?

Hint

The token is the answer.

Solution

Reviewers no longer debate "is #6750A4 the right purple?" on every change — the role name (primary) encodes the decision, which lives in one place (the theme).

Q19. [Advanced] (Theory) Argue why "it's just a small app" doesn't justify hardcoding.

Hint

"Just temporarily" + prototypes that ship.

Solution

Small apps grow and prototypes become production. Since the theme infrastructure is free and already present, hardcoding only defers a cost that increases with size — there's no upside to skipping the theme.

Q20. [Medium] (Theory) Summarize the inversion of control a theme introduces.

Hint

Who decides the color — the widget or the theme?

Solution

Control moves from the widget (asserting absolute values) to the theme (providing context-sensitive, role-named values). Widgets become consumers of style, not authors of it.


Section 2 — ThemeData (Q21–40)

Q21. [Basic] (Theory) What is ThemeData and where do you supply it?

Hint

The central style object → MaterialApp.

Solution

The immutable object holding colors, text styles, and component defaults. You pass it to MaterialApp's theme: (and darkTheme:).

Q22. [Basic] (Coding) Write the smallest ThemeData giving the app an orange Material 3 scheme.

Hint

One property; useMaterial3 is already default.

Solution
ThemeData(colorSchemeSeed: Colors.orange);

Q23. [Basic] (Theory) Is useMaterial3: true necessary in a new 2026 app?

Hint

Default since 3.16.

Solution

No — Material 3 is the default since Flutter 3.16, so you don't set it for new apps (set it explicitly only during a migration to make intent clear).

Q24. [Basic] (Theory) What is the single most important property of ThemeData?

Hint

The heart of the theme.

Solution

colorScheme — almost every Material widget colors itself from it.

Q25. [Medium] (Theory) Difference between colorSchemeSeed: and colorScheme: on ThemeData?

Hint

Convenience vs control.

Solution

colorSchemeSeed: takes a single seed color and builds the scheme for you (simplest). colorScheme: takes a ColorScheme you construct (e.g. ColorScheme.fromSeed(..., brightness: ...)), needed for brightness control or role tweaks.

Q26. [Medium] (Coding) Make every AppBar flat and centered without touching instances.

Hint

appBarTheme.

Solution
ThemeData(
  colorSchemeSeed: Colors.indigo,
  appBarTheme: const AppBarTheme(elevation: 0, centerTitle: true),
);

Q27. [Medium] (Theory) Why must you use copyWith to vary a ThemeData?

Hint

It's immutable.

Solution

ThemeData is immutable; you can't edit it in place. copyWith returns a new instance with some fields replaced and the rest copied.

Q28. [Medium] (Coding) From final base = ThemeData(colorSchemeSeed: Colors.teal);, make a compact-density variant.

Hint

visualDensity.

Solution
final compact = base.copyWith(visualDensity: VisualDensity.compact);

Q29. [Medium] (Theory) How does Theme.of(context) locate the theme, mechanically?

Hint

Walk up to the nearest inherited Theme.

Solution

It walks up the tree from context to the nearest Theme InheritedWidget (inserted by MaterialApp) and returns its ThemeData, subscribing the caller to rebuilds.

Q30. [Advanced] (Coding) Spot the bug: theme: ThemeData(colorSchemeSeed: Colors.pink) but Theme.of(context).colorScheme.primary is default blue.

Hint

Which context?

Solution

The context is from the same build that creates MaterialApp, i.e. above the theme, so it returns the default. Read from a child widget below MaterialApp (or wrap in a Builder).

Q31. [Medium] (Coding) Give every Card a 16px rounded shape globally.

Hint

cardTheme.

Solution
ThemeData(
  cardTheme: CardThemeData(
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
  ),
);

Q32. [Medium] (Theory) What is a ThemeExtension for?

Hint

Tokens Material doesn't know about.

Solution

For app-specific tokens beyond Material's vocabulary (e.g. a "success" color, brand gradient, custom spacing), attached via ThemeData(extensions: [...]).

Q33. [Basic] (Theory) Name three component sub-theme properties of ThemeData.

Hint

xxxTheme named after the widget.

Solution

Examples: appBarTheme, elevatedButtonTheme, cardTheme (also chipTheme, inputDecorationTheme, dialogTheme, …).

Q34. [Advanced] (Coding) Wrap a subtree so only it has red as primary, leaving the rest of the app unchanged.

Hint

Local Theme widget + copyWith on the scheme.

Solution
Theme(
  data: Theme.of(context).copyWith(
    colorScheme: Theme.of(context).colorScheme.copyWith(primary: Colors.red),
  ),
  child: const DangerZone(),
)

Q35. [Medium] (Theory) When would you use ThemeData.from(colorScheme: ...)?

Hint

You already hold a ColorScheme.

Solution

When you already have a ColorScheme object and want a theme built from it. In practice ThemeData(colorScheme: ...) covers most cases.

Q36. [Basic] (Theory) What do ThemeData.light() and ThemeData.dark() give you?

Hint

Pre-baked.

Solution

Ready-made light and dark Material themes — handy for quick starts, defaults, or fallbacks.

Q37. [Medium] (Coding) Build a theme whose AppBar colors come from the scheme (so it adapts to dark mode).

Hint

scheme.surface / scheme.onSurface.

Solution
final scheme = ColorScheme.fromSeed(seedColor: Colors.indigo);
ThemeData(
  colorScheme: scheme,
  appBarTheme: AppBarTheme(
    backgroundColor: scheme.surface,
    foregroundColor: scheme.onSurface,
  ),
);

Q38. [Advanced] (Theory) Why is reaching for theme roles in component sub-themes better than hardcoding colors there?

Hint

Sub-themes feed every instance.

Solution

A sub-theme applies to every widget of that type; if it hardcodes a color, that color won't adapt to dark/brand. Pulling from the scheme keeps the global style adaptive.

Q39. [Medium] (Theory) State the 80/20 rule for building a ThemeData.

Hint

Two or three properties.

Solution

Set colorSchemeSeed/colorScheme, optionally a textTheme, and a few component sub-themes — that's a complete professional theme; everything else has sensible defaults.

Q40. [Advanced] (Coding) A widget that reads Theme.of(context) doesn't update when you switch themes at the root. Most likely cause?

Hint

Did it actually read via of(context)?

Solution

It captured the theme outside build (e.g. stored it in a field/initState) so it isn't subscribed to the inherited theme; or it used a stale/const context. Read Theme.of(context) inside build so it re-subscribes and rebuilds on change.


Section 3 — ColorScheme & Material You (Q41–60)

Q41. [Basic] (Theory) What does ColorScheme.fromSeed produce?

Hint

One color in, many out.

Solution

A full ColorScheme (~30 coordinated roles) generated from one seed color via Material 3's tonal algorithm, tuned to harmonize and meet contrast.

Q42. [Basic] (Theory) What is the on convention (e.g. onPrimary)?

Hint

Content drawn on a background.

Solution

For a background role X, onX is the readable content color to draw on top of it. Always pair X with onX.

Q43. [Basic] (Coding) Text on a primary button — which color role?

Hint

Apply the on rule.

Solution

onPrimary.

Q44. [Medium] (Theory) Difference between primary and primaryContainer?

Hint

Bold vs soft.

Solution

primary is the bold, saturated brand color (filled FAB/buttons); primaryContainer is a softer tinted version (chips, highlighted cards), with onPrimaryContainer for its content.

Q45. [Medium] (Coding) Build a light and dark scheme from a single green seed.

Hint

Vary brightness.

Solution
final light = ColorScheme.fromSeed(seedColor: Colors.green);
final dark  = ColorScheme.fromSeed(seedColor: Colors.green, brightness: Brightness.dark);

Q46. [Medium] (Theory) Why generate light and dark from the same seed?

Hint

Shared hue → on brand.

Solution

They share a hue so they read as two views of one brand identity, while each is independently tuned for its background's contrast.

Q47. [Medium] (Coding) Fix: Container(color: scheme.error, child: Text('!', style: TextStyle(color: scheme.onSurface))).

Hint

Wrong on pairing.

Solution
Container(color: scheme.error, child: Text('!', style: TextStyle(color: scheme.onError)));

Q48. [Medium] (Theory) Which ColorScheme roles are deprecated, and what replaces them?

Hint

background, surfaceVariant.

Solution

backgroundsurface; onBackgroundonSurface; surfaceVariantsurfaceContainerHighest (or surfaceContainerHigh).

Q49. [Basic] (Theory) What role is the default background of cards/sheets/scaffold?

Hint

The component background role.

Solution

surface (with onSurface for content on it).

Q50. [Medium] (Theory) Why does setting a good ColorScheme theme most of the app "for free"?

Hint

Who reads the scheme automatically?

Solution

Built-in Material widgets color themselves from the scheme's roles (FAB → primaryContainer, card → surface, etc.), so a correct scheme styles all standard components automatically; you only reach for roles by hand in custom widgets.

Q51. [Medium] (Coding) Make a soft "Beta" badge with readable text using tertiary roles.

Hint

Container roles, not the saturated one.

Solution
Container(
  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
  decoration: BoxDecoration(color: scheme.tertiaryContainer, borderRadius: BorderRadius.circular(8)),
  child: Text('Beta', style: TextStyle(color: scheme.onTertiaryContainer)),
)

Q52. [Advanced] (Theory) What is the surfaceContainerLowest…Highest ladder for?

Hint

Layering/elevation tints.

Solution

A graduated set of surface tints used to express elevation/layering in Material 3 (a higher card sits on a higher container tone), replacing the older single surfaceVariant.

Q53. [Medium] (Theory) Which roles would you use for an OutlinedButton's border and a divider?

Hint

There's an outline family.

Solution

outline (and outlineVariant for subtler/decorative lines).

Q54. [Basic] (Theory) What's the best-practice default when creating a scheme?

Hint

Just a seed.

Solution

Use ColorScheme.fromSeed(seedColor: ...) with no role overrides — the generated values harmonize and pass contrast; override only when a brand spec forces it.

Q55. [Advanced] (Coding) Generate a higher-contrast scheme for accessibility from a seed.

Hint

There's a contrastLevel knob.

Solution
ColorScheme.fromSeed(seedColor: Colors.teal, contrastLevel: 1.0); // 0.0 default → 1.0 high

Q56. [Medium] (Theory) What is "Material You" / dynamic color, originally?

Hint

Where does the palette come from on Android 12+?

Solution

A palette derived from the user's wallpaper on Android 12+, obtained in Flutter via the dynamic_color package, with a seed scheme as fallback.

Q57. [Advanced] (Theory) Why does role-based design make swapping the entire color source painless?

Hint

Widgets reference roles, not hex.

Solution

Because widgets reference semantic roles, you can change where the ColorScheme comes from (seed, wallpaper, per-brand config) and every widget re-reads its role — no widget edits.

Q58. [Medium] (Coding) A snippet uses colorScheme.background. Modernize it.

Hint

Deprecated → surface.

Solution

Replace colorScheme.background with colorScheme.surface (and onBackgroundonSurface).

Q59. [Medium] (Theory) Which roles back a default FloatingActionButton in Material 3?

Hint

A container pair.

Solution

primaryContainer (background) and onPrimaryContainer (icon) by default.

Q60. [Advanced] (Coding) You override primary manually and now text on a primary button looks low-contrast. What went wrong and how do you avoid it?

Hint

onPrimary wasn't regenerated.

Solution

Overriding primary alone leaves onPrimary unchanged, so the pair is no longer contrast-matched. Either avoid manual overrides (regenerate from a new seed) or override onPrimary to match — the safe path is changing the seed, not individual roles.


Section 4 — TextTheme & typography (Q61–80)

Q61. [Basic] (Theory) Name the five Material 3 text families.

Hint

Biggest to smallest.

Solution

display, headline, title, body, label — each with Large/Medium/Small.

Q62. [Basic] (Coding) Apply titleMedium to a Text.

Hint

Theme.of(context).textTheme.

Solution
Text('Title', style: Theme.of(context).textTheme.titleMedium);

Q63. [Medium] (Theory) Which family/size for an AppBar title? For body paragraphs? For a button label?

Hint

title / body / label.

Solution

AppBar title → titleLarge; paragraphs → bodyLarge/bodyMedium; button label → labelLarge.

Q64. [Medium] (Coding) Show titleLarge text in the theme's primary color, keeping its size.

Hint

copyWith on the style.

Solution
Text('Pro', style: Theme.of(context).textTheme.titleLarge?.copyWith(
  color: Theme.of(context).colorScheme.primary,
));

Q65. [Medium] (Theory) Why prefer copyWith/merge over constructing a fresh TextTheme(...)?

Hint

What happens to the unlisted styles?

Solution

A fresh TextTheme(...) only populates the styles you pass; the others become null. copyWith/merge keep all 15 and override only the overlaps.

Q66. [Advanced] (Coding) Apply the "Inter" Google font to all text while preserving sizes.

Hint

GoogleFonts.interTextTheme(base.textTheme).

Solution
final base = ThemeData(colorSchemeSeed: Colors.indigo);
ThemeData(
  colorScheme: base.colorScheme,
  textTheme: GoogleFonts.interTextTheme(base.textTheme),
);

Q67. [Medium] (Coding) Bug: setting textTheme: const TextTheme(bodyLarge: TextStyle(fontSize: 17)) makes titleMedium text disappear. Fix it.

Hint

You replaced instead of merged.

Solution
final base = ThemeData(colorSchemeSeed: Colors.indigo);
base.copyWith(
  textTheme: base.textTheme.copyWith(bodyLarge: const TextStyle(fontSize: 17)),
);

Q68. [Basic] (Theory) Where should a text color come from?

Hint

So it adapts to dark mode.

Solution

From the colorScheme (onSurface, onSurfaceVariant, primary, …) — not a hardcoded value — so it adapts between light and dark.

Q69. [Medium] (Theory) Give the "word processor" analogy for TextTheme.

Hint

Heading styles vs manual formatting.

Solution

TextTheme is the document's style sheet; titleLarge/bodyMedium are named styles like "Heading 2." You apply the named style instead of manual sizes, and editing the sheet updates everywhere.

Q70. [Medium] (Coding) Difference in effect: base.merge(other) vs TextTheme(...) for combining text themes.

Hint

Keep vs discard.

Solution

base.merge(other) keeps all of base's styles and overrides the overlaps from other; a fresh TextTheme(...) discards everything not explicitly set.

Q71. [Advanced] (Coding) Apply a brand font app-wide AND resize headlineSmall, keeping dark-mode adaptivity. Outline the code order.

Hint

Font first (merge to all), then copyWith one style; leave colors unset.

Solution
final base = ThemeData(colorSchemeSeed: Colors.indigo);
final tt = GoogleFonts.poppinsTextTheme(base.textTheme).copyWith(
  headlineSmall: base.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w700),
);
ThemeData(colorScheme: base.colorScheme, textTheme: tt);

Leave text colors unset so the scheme supplies them per mode.

Q72. [Basic] (Theory) How many styles are in a full Material 3 TextTheme?

Hint

5 families × 3 sizes.

Solution

Q73. [Medium] (Coding) Make a low-emphasis caption that adapts to dark mode.

Hint

labelSmall + onSurfaceVariant.

Solution
final t = Theme.of(context);
Text('Updated now', style: t.textTheme.labelSmall?.copyWith(
  color: t.colorScheme.onSurfaceVariant,
));

Q74. [Medium] (Theory) Why might a textTheme style be null, and how do you guard?

Hint

Stripped theme; ?. / ??.

Solution

If a custom TextTheme left it unset. Guard with ?.copyWith(...) or style ?? const TextStyle(). The default Material theme populates all 15.

Q75. [Advanced] (Theory) How do Parts 3 and 4 combine in a single Text?

Hint

Size from one, color from the other.

Solution

Take size/weight from textTheme.<style> and color from colorScheme.<role> (often via .copyWith(color: ...)), so the text is both correctly scaled and correctly colored per mode.

Q76. [Medium] (Coding) Render a title + paragraph + tiny timestamp with appropriate named styles.

Hint

headline / body / label.

Solution
final t = Theme.of(context).textTheme;
Column(children: [
  Text('Dashboard', style: t.headlineSmall),
  Text('Welcome back.', style: t.bodyMedium),
  Text('2m ago', style: t.labelSmall),
]);

Q77. [Basic] (Theory) What's the typography equivalent of "swap the seed color"?

Hint

One line re-skins all text.

Solution

GoogleFonts.<font>TextTheme(base.textTheme) — one call applies a typeface to all 15 styles.

Q78. [Medium] (Theory) Should body text usually carry an explicit color? Why or why not?

Hint

Let the theme apply onSurface.

Solution

Often no — leaving it unset lets the theme apply the correct onSurface color automatically (adapting to light/dark). Set a color only for emphasis/intent, pulled from the scheme.

Q79. [Advanced] (Coding) You need two visually distinct heading weights but the same font everywhere. Minimal approach?

Hint

Font to all, then copyWith two heading styles.

Solution

Apply the font via GoogleFonts.xTextTheme(base.textTheme), then .copyWith(headlineLarge: ...w700, headlineSmall: ...w500). All other styles keep the font and defaults.

Q80. [Medium] (Theory) Why is choosing a named style "intent over size" valuable?

Hint

Tune the scale once.

Solution

You express what the text is (title, body, label); the scale defines how big in one place. Adjusting the scale updates every screen, and the code reads semantically.


Section 5 — Light & dark mode (Q81–100)

Q81. [Basic] (Theory) Which three MaterialApp properties implement light/dark?

Hint

Two themes + a selector.

Solution

theme, darkTheme, and themeMode.

Q82. [Basic] (Theory) What are the three ThemeMode values?

Hint

Force one or follow OS.

Solution

ThemeMode.light, ThemeMode.dark, ThemeMode.system.

Q83. [Basic] (Coding) Configure MaterialApp to follow the OS setting.

Hint

themeMode: ThemeMode.system + both themes.

Solution
MaterialApp(
  theme: lightTheme,
  darkTheme: darkTheme,
  themeMode: ThemeMode.system,
  home: const HomePage(),
);

Q84. [Medium] (Theory) For ThemeMode.system to ever show dark, what must you provide?

Hint

Otherwise it falls back.

Solution

A non-null darkTheme. Without it, system falls back to theme even when the OS is dark.

Q85. [Medium] (Coding) Helper returning a ThemeData for a given Brightness from one seed.

Hint

Pass brightness to fromSeed.

Solution
ThemeData themeFor(Brightness b) =>
  ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF6750A4), brightness: b));

Q86. [Medium] (Theory) Mechanically, why do themed widgets adapt to dark mode without per-widget changes?

Hint

Inherited theme changes → rebuild.

Solution

themeMode swaps the active ThemeData; the inherited Theme changes; dependents that read colorScheme/textTheme rebuild and re-read the dark values — same widget code, dark output.

Q87. [Medium] (Coding) Fix the dark-mode-breaking card: Container(color: Colors.white, child: Text('Hi', style: TextStyle(color: Colors.black))).

Hint

surface / onSurface.

Solution
final s = Theme.of(context).colorScheme;
Container(color: s.surface, child: Text('Hi', style: TextStyle(color: s.onSurface)));

Q88. [Basic] (Theory) What's the "dark-mode test" for a custom widget?

Hint

Look for hardcoded white/black/hex.

Solution

Ask "did I hardcode a background or text color (Colors.white/Colors.black/hex)?" If yes, it'll break in dark mode — replace with a scheme role.

Q89. [Medium] (Theory) Theme.of(context).brightness vs MediaQuery.platformBrightnessOf(context) — which respects an in-app override?

Hint

Effective theme vs OS setting.

Solution

Theme.of(context).brightness reflects the effective applied theme (respects an in-app themeMode override). MediaQuery.platformBrightnessOf reports only the OS setting.

Q90. [Medium] (Coding) Swap a logo asset based on the effective app brightness.

Hint

Theme.of(context).brightness == Brightness.dark.

Solution
final isDark = Theme.of(context).brightness == Brightness.dark;
final logo = isDark ? 'assets/logo_light.png' : 'assets/logo_dark.png';

Q91. [Advanced] (Theory) Why keep the seed and text scale shared between light and dark, varying only brightness?

Hint

Brand consistency + less to maintain.

Solution

It keeps both modes on-brand (same hue and typography) and minimizes maintenance — there's one source of truth and a single deliberate difference (brightness) rather than two hand-authored palettes that can drift.

Q92. [Medium] (Theory) Give the sunglasses analogy for light/dark.

Hint

Build the world once; change the lens.

Solution

Your widgets are the world (built once). theme/darkTheme are two pairs of glasses (clear/tinted); themeMode chooses which is worn. The world doesn't change — the lens does.

Q93. [Advanced] (Coding) Update the status-bar icon brightness to match a dark theme.

Hint

SystemChrome.setSystemUIOverlayStyle.

Solution
SystemChrome.setSystemUIOverlayStyle(
  isDark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
);

(SystemUiOverlayStyle.light = light icons, for a dark background.)

Q94. [Medium] (Theory) What's the natural next step after themeMode: ThemeMode.system?

Hint

Make it state.

Solution

Make themeMode user-controllable state (a Light/Dark/System setting) and persist it — the job of the Riverpod theming series.

Q95. [Advanced] (Theory) Why does an AppBar that pulls scheme.surface/scheme.onSurface "just work" in dark mode?

Hint

Roles resolve per scheme.

Solution

Those roles resolve to light values in the light scheme and dark values in the dark scheme. Since the AppBar references roles (not hex), switching the active scheme re-colors it correctly with no change.

Q96. [Medium] (Coding) Provide both themes from one helper and wire them up.

Hint

themeFor(light) / themeFor(dark).

Solution
MaterialApp(
  theme: themeFor(Brightness.light),
  darkTheme: themeFor(Brightness.dark),
  themeMode: ThemeMode.system,
  home: const HomePage(),
);

Q97. [Basic] (Theory) Does adding dark mode require packages?

Hint

It's built into MaterialApp.

Solution

No — theme/darkTheme/themeMode are built into MaterialApp.

Q98. [Advanced] (Theory) A user forces dark in-app but the OS is light; a widget shows a light asset. Which brightness API was (wrongly) used?

Hint

It read the OS, not the app.

Solution

MediaQuery.platformBrightnessOf(context) (OS setting), which ignores the in-app override. It should use Theme.of(context).brightness.

Q99. [Medium] (Theory) Why is ThemeMode.system called the "respectful default"?

Hint

Honors an existing choice.

Solution

It honors the light/dark choice the user already made in their OS settings, rather than imposing one — and auto-switches when they change it.

Q100. [Advanced] (Coding) Outline a complete, correct light/dark MaterialApp from a single seed, shared text scale, and adaptive AppBar.

Hint

Combine Parts 2–5.

Solution
const seed = Color(0xFF6750A4);
ThemeData themeFor(Brightness b) {
  final s = ColorScheme.fromSeed(seedColor: seed, brightness: b);
  return ThemeData(
    colorScheme: s,
    textTheme: appTextTheme,
    appBarTheme: AppBarTheme(backgroundColor: s.surface, foregroundColor: s.onSurface, elevation: 0),
  );
}
MaterialApp(
  theme: themeFor(Brightness.light),
  darkTheme: themeFor(Brightness.dark),
  themeMode: ThemeMode.system,
  home: const HomePage(),
);

Coding Mini-Exercises

Ten larger problems that combine multiple parts. Each has a full solution — but build it yourself first.

Exercise 1 — De-hardcode a card. You're given a card that hardcodes a purple background, white title, and grey subtitle. Rewrite it to use theme roles so it adapts to dark mode.

Show solution
Widget themedCard(BuildContext context) {
  final s = Theme.of(context).colorScheme;
  final t = Theme.of(context).textTheme;
  return Card(
    color: s.surfaceContainerHigh,
    child: ListTile(
      title: Text('Premium', style: t.titleMedium?.copyWith(color: s.onSurface)),
      subtitle: Text('Unlock all features',
          style: t.bodySmall?.copyWith(color: s.onSurfaceVariant)),
    ),
  );
}

Every color now comes from the scheme; the text sizes from the type scale.

Exercise 2 — Seed-driven theme factory. Write a function appTheme(Color seed, Brightness b) returning a complete ThemeData with an adaptive AppBar and rounded cards.

Show solution
ThemeData appTheme(Color seed, Brightness b) {
  final s = ColorScheme.fromSeed(seedColor: seed, brightness: b);
  return ThemeData(
    colorScheme: s,
    appBarTheme: AppBarTheme(
      backgroundColor: s.surface, foregroundColor: s.onSurface, elevation: 0,
    ),
    cardTheme: CardThemeData(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
    ),
  );
}

Exercise 3 — Global button shape. Make every ElevatedButton and FilledButton use a 12px radius and labelLarge-styled text, app-wide.

Show solution
ThemeData(
  colorSchemeSeed: Colors.indigo,
  elevatedButtonTheme: ElevatedButtonThemeData(
    style: ElevatedButton.styleFrom(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
    ),
  ),
  filledButtonTheme: FilledButtonThemeData(
    style: FilledButton.styleFrom(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
    ),
  ),
);

Exercise 4 — Custom font app-wide. Apply "Poppins" to all text and bump headings to w700, preserving every other style.

Show solution
final base = ThemeData(colorSchemeSeed: Colors.deepPurple);
final tt = GoogleFonts.poppinsTextTheme(base.textTheme).copyWith(
  headlineLarge: base.textTheme.headlineLarge?.copyWith(fontWeight: FontWeight.w700),
  headlineMedium: base.textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.w700),
  headlineSmall: base.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w700),
);
final theme = ThemeData(colorScheme: base.colorScheme, textTheme: tt);

Exercise 5 — Local override. Wrap a "danger zone" subtree so its primary is red, without affecting the rest of the app.

Show solution
Theme(
  data: Theme.of(context).copyWith(
    colorScheme: Theme.of(context).colorScheme.copyWith(
      primary: Colors.red, onPrimary: Colors.white,
    ),
  ),
  child: const DangerZone(),
)

Overriding onPrimary too keeps the contrast pair correct.

Exercise 6 — Status chip set. Build three status chips (Success/Warning/Error) using container roles for soft fills and matching on colors.

Show solution
Widget chip(BuildContext c, String label, Color bg, Color fg) => Container(
  padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
  decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(999)),
  child: Text(label, style: TextStyle(color: fg)),
);

// In build():
final s = Theme.of(context).colorScheme;
Wrap(spacing: 8, children: [
  chip(context, 'Error', s.errorContainer, s.onErrorContainer),
  chip(context, 'Info', s.secondaryContainer, s.onSecondaryContainer),
  chip(context, 'New', s.tertiaryContainer, s.onTertiaryContainer),
]);

(Material doesn't ship "success/warning" roles — for true brand semantic colors you'd add a ThemeExtension, the next theming topic.)

Exercise 7 — Brightness-aware widget. Build a widget that shows "Dark mode is ON/OFF" reflecting the effective theme, and a divider using outlineVariant.

Show solution
Widget status(BuildContext context) {
  final isDark = Theme.of(context).brightness == Brightness.dark;
  final s = Theme.of(context).colorScheme;
  return Column(children: [
    Text('Dark mode is ${isDark ? "ON" : "OFF"}'),
    Divider(color: s.outlineVariant),
  ]);
}

Exercise 8 — Fix the missing-styles bug. A theme set textTheme: const TextTheme(bodyLarge: TextStyle(fontSize: 16)) and now titles render unstyled. Repair without losing the size tweak.

Show solution
final base = ThemeData(colorSchemeSeed: Colors.teal);
final theme = base.copyWith(
  textTheme: base.textTheme.copyWith(
    bodyLarge: base.textTheme.bodyLarge?.copyWith(fontSize: 16),
  ),
);

Merging onto base.textTheme keeps the other 14 styles.

Exercise 9 — Two-brand factory. Given a Brand enum (acme, globex) each with its own seed, return the right light/dark theme pair for a selected brand.

Show solution
enum Brand { acme, globex }
const _seeds = {Brand.acme: Color(0xFF6750A4), Brand.globex: Color(0xFF00696D)};

ThemeData brandTheme(Brand brand, Brightness b) =>
  ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: _seeds[brand]!, brightness: b));

// usage:
MaterialApp(
  theme: brandTheme(Brand.acme, Brightness.light),
  darkTheme: brandTheme(Brand.acme, Brightness.dark),
  themeMode: ThemeMode.system,
);

Switching the Brand reskins the entire app — the whitelabel payoff from Part 1.

Exercise 10 — Capstone: a fully themed mini-app. Assemble a single-file app that: builds light+dark themes from one seed with a shared custom-font text scale, styles AppBar/Card/Button globally, follows the system theme, and renders a screen (AppBar + a card + a primary button + a soft status chip) using only theme roles — zero hardcoded colors in the screen.

Show solution
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

const _seed = Color(0xFF6750A4);

ThemeData _themeFor(Brightness b) {
  final s = ColorScheme.fromSeed(seedColor: _seed, brightness: b);
  final base = ThemeData(colorScheme: s, useMaterial3: true);
  return base.copyWith(
    textTheme: GoogleFonts.interTextTheme(base.textTheme),     // Part 4
    appBarTheme: AppBarTheme(                                  // Part 2
      backgroundColor: s.surface, foregroundColor: s.onSurface, elevation: 0,
    ),
    cardTheme: CardThemeData(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
    ),
    filledButtonTheme: FilledButtonThemeData(
      style: FilledButton.styleFrom(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      ),
    ),
  );
}

void main() => runApp(const CapstoneApp());

class CapstoneApp extends StatelessWidget {
  const CapstoneApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
    theme: _themeFor(Brightness.light),       // Part 3 + 5
    darkTheme: _themeFor(Brightness.dark),
    themeMode: ThemeMode.system,              // Part 5
    home: const HomeScreen(),
  );
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});
  @override
  Widget build(BuildContext context) {
    final s = Theme.of(context).colorScheme;  // Part 3
    final t = Theme.of(context).textTheme;    // Part 4
    return Scaffold(
      appBar: AppBar(title: const Text('Theming Capstone'), centerTitle: true),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Card(
              child: ListTile(
                title: Text('Welcome', style: t.titleMedium),       // role color via theme
                subtitle: Text('Fully themed — no hardcoded colors.',
                    style: t.bodyMedium?.copyWith(color: s.onSurfaceVariant)),
                trailing: Container(
                  padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
                  decoration: BoxDecoration(
                    color: s.tertiaryContainer,
                    borderRadius: BorderRadius.circular(999),
                  ),
                  child: Text('New', style: t.labelSmall?.copyWith(
                    color: s.onTertiaryContainer)),
                ),
              ),
            ),
            const SizedBox(height: 16),
            FilledButton(onPressed: () {}, child: const Text('Get started')),
          ],
        ),
      ),
    );
  }
}

Which part each piece exercises: the seed→scheme and roles are Part 3; ThemeData assembly, copyWith, and global sub-themes are Part 2; the custom-font textTheme and named styles are Part 4; theme/darkTheme/themeMode and role-based adaptivity are Part 5; and the whole "no hardcoded colors in the screen" discipline is the thesis of Part 1. Run it, flip your OS to dark, and watch every pixel adapt without touching HomeScreen.


You made it

A hundred questions, ten exercises, and a capstone later, you can build a Flutter theme the right way: a seed-driven ColorScheme and type scale assembled into ThemeData, wired for light and dark — all because you refused to hardcode colors.

The one thing this series didn't do: let the user choose the theme at runtime and have the app remember it. That's the whole point of the sibling course — Mastering Riverpod: Theming — where themeMode becomes reactive, persistent state. See you there.