Skip to main content
Sundial delivers tokens through Flutter’s ThemeExtension API — the platform-native way to attach custom data to a ThemeData.

The two extensions

Both are installed by AppTheme:

Context accessors

theme_extensions.dart provides the two getters used throughout the app:
Two properties worth noting:
  • They resolve against the nearest Theme — inside a ThemeOverride subtree you get the overridden tokens, not the app-level ones.
  • They are non-null asserted (!) — any Theme a Sundial widget lives under must carry both extensions. In practice that means themes must come from AppTheme.*; a bare ThemeData() would crash token consumers. When overriding a subtree theme, always go through AppTheme.light/.dark or ThemeOverride.
Don’t confuse context.textTheme (Sundial’s extension getter returning DesignSystemTypography) with Flutter’s Theme.of(context).textTheme (Material’s TextTheme). The names are close; the types differ.

Why copyWith and lerp matter

Every ThemeExtension subclass must implement copyWith and lerp. Sundial implements both for every field (all 25 colors, all 15 text styles, and the 5 accent fields):
  • copyWith is how AppTheme nests the resolved accent: baseColorTheme.copyWith(accent: accent).
  • lerp is what makes theme changes animate. When ThemeData changes (brightness flip, persona change, ThemeOverride boundary), Flutter’s AnimatedTheme interpolates between old and new extensions — every color cross-fades and every text style transitions smoothly, with zero widget-level work.

Adding a token (checklist)

Adding a field to either extension means updating four places in the token file — the field, the constructor, copyWith, and lerp — plus the .light/.dark (or .base) instances. Missing lerp won’t fail to compile for nullable fields, so review carefully; a token that doesn’t lerp will snap during animated theme changes. See Contributing.