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 createapp 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
background → surface; onBackground → onSurface; surfaceVariant → surfaceContainerHighest (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 onBackground → onSurface).
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.