🚀 Join the waitlist now! waitlist.floot.dev
LogoFlootdocs

Theming

How colors, fonts and component themes are wired, and the shortest path to making the app look like yours.

The class you cannot autocomplete

Every color in the app is declared in lib/core/presentation/utils/constants/colors.dart, which holds two classes: FlootColors, around 127 public semantic aliases such as appBarBgLight, cardSurfaceDark and badgeError; and _ColorTokens, roughly 73 private palette tokens such as primary500, neutral950 and error600.

_ColorTokens is private. It never appears in autocomplete, and nothing outside that one file can reference it. A buyer who opens the app, types FlootColors. and edits from there will end up repointing aliases one at a time and miss the small change that would have recolored everything at once.

That indirection is the whole design. _ColorTokens defines seven families, primary, secondary, neutral, success, error, warning, info, at ten shades each (neutral gets eleven, plus black and white), and FlootColors maps roles onto them. It does not use a ColorScheme pair: it declares explicit *Light / *Dark siblings, most aliases coming in pairs.

lib/core/presentation/utils/constants/colors.dart
static final Color primary500 = Colors.deepPurple.shade500;   // token
static const Color neutral950 = Color.fromRGBO(17, 17, 17, 1); // token

static const Color cardSurfaceLight = _ColorTokens.neutralWhite; // alias
static const Color cardSurfaceDark = _ColorTokens.neutral950;    // alias

Edit tokens to rebrand. Edit aliases only to change what a specific role means.

There is a second color system running alongside

lib/core/presentation/utils/theme/components/color_scheme.dart builds a real Material ColorScheme by seeding off two aliases:

lib/core/presentation/utils/theme/components/color_scheme.dart
static final Color _seedColor = FlootColors.primary;        // primary500
static final Color _secondaryColor = FlootColors.secondary; // secondary500

static ColorScheme lightTheme = ColorScheme.fromSeed(seedColor: _seedColor, secondary: _secondaryColor);

theme.dart hands that scheme to ThemeData, and also to seventeen hand-written component themes that set their colors from FlootColors directly. Knowing which one you are editing matters: the explicit aliases win anywhere a component theme covers the widget, app bars, cards, dialogs, buttons, inputs, checkboxes, list tiles, the navigation bar; the seeded ColorScheme wins everywhere else, meaning Material widgets with no component theme, and anything that reads Theme.of(context).colorScheme, which a fresh project never does.

So the scheme is a safety net for widgets you add later, not the source of truth for what ships. Change primary500 and both systems move together; change an alias and only the first one does.

Rebranding, in six edits

Repoint the brand families. In _ColorTokens, swap primary50 through secondary900 (colors.dart): replace Colors.deepPurple and Colors.purple with your own swatch, or drop the Material dependency and write hex values. This is the edit that also moves the seeded ColorScheme.

Catch the two escapees. Two aliases bypass the token layer entirely and reach for a Material swatch directly:

lib/core/presentation/utils/constants/colors.dart
static final Color tableHeaderSurfaceLight = Colors.blueGrey.shade50;
static final Color tableHeaderSurfaceDark = Colors.blueGrey.shade900;

They are the only two, and no token change reaches them.

Fonts, in lib/core/presentation/utils/theme/theme.dart:

lib/core/presentation/utils/theme/theme.dart
static const String _fontFamily = 'Poppins';
static final TextTheme _lightTextTheme = GoogleFonts.poppinsTextTheme();
static final TextTheme _darkTextTheme = GoogleFonts.poppinsTextTheme(
  ThemeData.dark().textTheme,
);

The string and the google_fonts calls are independent, so changing one alone gives you a half-applied font. Change all three.

Splash colors, in the flutter_native_splash block of pubspec.yaml. The dark value #111111 is neutral950 written out by hand, so if you retint your neutrals, edit it here too.

Terminal
dart run flutter_native_splash:create

App icons and the logo. Replace the sources in assets/icons/ and assets/app/logo.svg, then regenerate:

Terminal
dart run flutter_launcher_icons

Customization covers which file feeds which platform, and the second place the logo lives (the transactional email templates).

The web manifest, web/manifest.json, still carrying Flutter's stock blue, which no generator overwrites:

web/manifest.json
"background_color": "#0175C2",
"theme_color": "#0175C2",

You are re-running the generators, not running them

floot create already ran flutter_launcher_icons and flutter_native_splash:create once, against the placeholder assets. That is why the native icon and splash already look like something. Replacing the source images does nothing on its own, the generated platform files are what the build reads.

Sizes and text styles

sizes.dart (spacing, radii, font sizes, borrowed from Tailwind's scale) and styles.dart (composed TextStyles and shapes) sit beside colors.dart in lib/core/presentation/utils/constants/, doc-commented per constant.

Two things that will bite you

Retokenising is safe; collapsing roles is not

No test in the project hardcodes a color; assertions reference FlootColors.* by name, so you can repoint any token without touching a test.

What does break is making two roles equal. Several tests assert that colors differ, not just that they match, expecting primary, then warning, then destructive as, for example, a usage bar fills. Point two of those at one value and the test suite tells you, which is the good outcome. Point them at one value and adjust the test, and you have silently removed a signal from the UI.

Theme regressions are largely untested

The shared widget-test harness does not use your theme:

test/test_helpers.dart
theme: ThemeData(platform: platform),
darkTheme: ThemeData.dark(),

Widget tests run against stock Material, not FlootAppTheme. A component theme you break, a contrast failure, a color wired to the wrong alias, will not turn a test red. Check theme work by running the app in both modes.

On this page