Deep Dives

This chapter provides deeper and more detailed information and explanations on included topics. It will receive updates and new deep dives as needed in future updates, based on questions user of FlexColorScheme have.

This chapter may contain images and animations from older versions of FlexColorScheme. They are used when the principles presented are still the same and the actual images matter less. A future version of this chapter may update such images to be from the current version of FlexColorScheme if it is needed to improve the explanations.

Visual Density

Examples 3, 4 and 5 in this package use comfortable adaptive platform visual density via FlexColorScheme.comfortablePlatformDensity, instead of the default counter application's VisualDensity.adaptivePlatformDensity.

This is an alternative visual density design that on desktop applications results in the Flutter comfortable visual density being used, instead of compact. On devices, they both result in the default large standard visual density that is suitable for small touch devices.

This helper function was added to provide an easy option for using a bit larger UI elements, on desktop and web apps, while keeping the correct size for devices.

If the desktop and web versions of the app is used on computers with touch screens, the comfortable density provides a nice balance. It still looks compact enough to be desktop-like, while providing a bit more touch-friendly space, without looking like an overblown small device UI on a desktop.

FlexColorScheme can also use the VisualDensity.adaptivePlatformDensity value. If you prefer it, just replace the line with it. If you do not specify any visual density, the Flutter default density standard is used on all platforms. This creates widgets with a lot of white-space around and inside them. It may not be what you want on web/desktop applications, but it is the correct choice for small touch devices. The visual density feature in Flutter was created to address this difference in design requirement.

The Flutter SDK built-in function VisualDensity.adaptivePlatformDensity was added to adapt the density according to the used platform. The FlexColorScheme.comfortablePlatformDensity does the same, but with a bit more white-space on desktops. Use the one you like and work best for your use case.

FlexThemeModeSwitch

One feature on the HomePage of examples 1 to 4 is the FlexThemeModeSwitch. It is the UI Widget used for the 3-way theme mode switch used in these examples to change the active theme mode.

theme mode switch

Using the switch is simple, give it the currently selected and active theme mode, the current FlexSchemeData scheme, so it can color its buttons correctly. Then use the onThemeModeChanged callback for changes to its mode, and change the themeMode property in the MaterialApp accordingly, to actually change the used theme mode.

FlexThemeModeSwitch(
  themeMode: themeMode,
  onThemeModeChanged: onThemeModeChanged,
  flexSchemeData: flexSchemeData,
),

Using the FlexThemeModeSwitch 3-way theme mode switch is optional and not required to use FlexColorScheme based themes. It is just a custom theme mode switch design and was included as a bonus feature in the FlexColorScheme package. It was added based on a request after it had been observed in the wild in the old Flexfold demo app.

In the Flexfold demo app, the switch was originally a fairly fixed design. This FlexThemeModeSwitch has many properties that allow you to customize it extensively. You can find its API reference here and its companion, the FlexThemeModeOptionButton API reference here. With the API you can customize the look of the FlexThemeModeSwitch, here are some examples:

theme mode customize

The FlexThemeModeOptionButton is typically used by the FlexThemeModeSwitch, but it can also be used as a part of other theme related indicator widgets. Like the scrolling horizontal list used in example 5, where it is used as a theme indicator and selector in a horizontal scrolling list.

theme indicator list

Computed Dark Theme

Example 5 allows us to toggle the dark mode, from using its hand-tuned predefined dark scheme colors, to the dark scheme colors computed from the light scheme colors. Let's use it to compare some examples.

When using the Deep blue sea scheme, the computed dark theme colors are a bit more dull and muted in this example. The computed dark scheme is on the right.

ColorScheme example 5 dark   ColorScheme example 5 dark computed
Designed dark Deep blue see theme (left) versus computed dark theme (right) from its light theme.

With some other color schemes, like the Aqua blue one, there is only a minor difference. The computed dark scheme is on the right.

ColorScheme example 5a2 dark   ColorScheme example 5 dark computed
Designed dark Aqua blue theme (left) versus computed dark theme (right) from its light theme.

The result of the toDark method varies depending on how saturated the used light scheme colors are. It is possible to tune the calculated dark scheme by modifying the whiteBlend property it uses to blend in white to make the dark scheme. The default whiteBlend is 35%, this is normally a suitable value. For more saturated light scheme colors, try 40%, which is also used in the Material-2 design guide to convert the default red error color for light mode, to dark mode. For light scheme color with low saturation, a white blend of 20...30% often produces nice results.

With the included level slider in example 5, we can interactively change the whiteBlend level for the computed dark mode scheme colors. Let's select a color scheme, say the Brand blues one, then go dark.

By default, the built-in predefined hand-picked matching dark scheme colors for the dark theme mode are used. Turn on the "Compute dark theme" mode. The result is pretty close to the predefined one for this dark scheme with the default level of 35%. Then adjust the white level blend to tune how saturated the computed dark scheme colors are compared to their light scheme master. At 0 %, they are the same as the light scheme, at 100 %, well then they are white, not so useful. A range of 10...50% can produce excellent results. What is best depends on how saturated your starting light scheme colors are, and what kind of matching dark theme you like and want.

If you use the even darker dark-mode, true black, you may want to have a different saturation for your dark scheme colors compared to standard dark-mode surface. You could easily implement that adjustment with this feature.

This screen recording compares the computed toDark theme result, to the built-in hand-picked one. It does this by toggling the mode a few times, so you can compare the different results. It also uses the level slider to adjust the toDark theme result. A sharp eye might notice that this recording does not use the defaultError error color modifier, the changing dark error color does not look so nice when tuning the computed scheme colors. The bundled example 5 and live web version of it uses the modifier.

Dark scheme calc level

So that you can find the same setup in the new version 5 of the Themes Playground, here is an image of it when using the same brandBlue scheme and computing the dark theme from light one at level 35%. You can see the code in the image too. The key part is this:

darkTheme: FlexThemeData.dark(
  colors: FlexColor.schemes[FlexScheme.brandBlue]!
      .light.defaultError.toDark(35, false),

V5 toDark-1

In FlexColorScheme V5 that has container colors for each main color in the ColorScheme, a new way to produce the computed dark mode colors was added. If you for your dark mode, swap main and container colors, the colors will usually work well as they are in dark mode, at least if you followed the design principle for the main color and its container color in light theme mode.

You can then pass 'true' to the toDark method, indicating you have a "true" M3 light theme mode ColorScheme design. The toDark computation will swap the colors from the light mode colors and use it as a starting point for the dark theme mode. This also produces a more design correct dark mode, with respect to the shade tones used on the main color and its container color. You may not even need to add any white blend level to desaturate the colors. Below, we use 5% to demonstrate that you can.

darkTheme: FlexThemeData.dark(
  colors: FlexColor.schemes[FlexScheme.brandBlue]!
      .light.defaultError.toDark(5, true),

V5 toDark-2

If your light theme mode colors follow the M3 design intent, then always use the toDark(level, true) API. If you on the other have colors that are designed with older M2 design intent, then using toDark(level) as before, which defaults to toDark(level, false) is usually a better choice.

Convenient AppBar Theming

Let's study what FlexColorScheme can do with the AppBarTheme and how you can match it to your surface blending if you like.

You can easily toggle both dark and light mode AppBars to use differently themed backgrounds. By default, Material design uses AppBars with ColorScheme.primary color for light theme mode, and the dark background color in dark theme mode. Without using a separately defined sub AppBarTheme, FlexColorScheme AppBars can use different themed backgrounds based on an enum value.

The themed AppBar background can use scheme primary color, default Material plain white/dark background color, primary branded surface, primary branded background color, or a custom AppBar color.

The FlexColorScheme scheme's appBarColor is a separate scheme color that does not exist in Flutter's standard ColorScheme, so it does not have to be any of the colors available in a ColorScheme.

The predefined schemes use the color defined in a ColorScheme scheme's tertiary color, as their value for the custom appBarColor. When you make your own schemes, you can do the same or use a totally none ColorScheme related color as the AppBar's custom color option. This color then becomes one of the FlexColorScheme's easy selectable AppBar theme color options, via the appBarStyle property and the FlexAppBarStyle enum, in this case via the custom choice.

Below, you can see some different primary color blend level using background colors used as the themed AppBar background color.

ColorScheme example 5d light   ColorScheme example 5e light   ColorScheme example 5f light   ColorScheme example 5g light
Using background colored AppBar theme, that includes the primary color blend at different levels in the background

The TabBar Style

The tabBarStyle property can be used to toggle the theme the TabBar receives. By default, a theme that is designed to make it fit in an AppBar, regardless of which style you have selected for it, is used. This is the FlexTabBarStyle.forAppBar style. The typical usage of a TabBar is to have it in an AppBar, and the default style works for this use case.

Alternatively you can choose a style that makes a TabBarTheme that fits well on background colors. Use this option if you intend to use the TabBar in a Scaffold body, in Dialogs, Drawers or other surface and background colored Material. If you do so, you do not have re-theme it, or style it separately for this purpose.

If you intend to use TabBar widgets in both AppBars and on surface and backgrounds, you will have to choose the style that most often fits your use case. Then theme it separately for the other use case. You would have to do the same with Flutter standard themes and TabBarTheme as well when not using FlexColorScheme, but the first theme your get without effort.

Which tabBarStyle style and resulting TabBarTheme actually works best, depends on the background color. Here we see TabBars used on surfaces and in an AppBar, when the AppBar is using primary color. As can be seen, the tab bar theme that goes well in an app bar in such a case, does not fit on the surfaces at all, and wise versa.

tab bar on primary

If you plan to use only surface or background (also the branded ones) colored AppBars, you can see that both TabBar styles, and their resulting themes, work for both situations. The difference is minor, and it is a matter of opinion which one is preferable. Both style options can be used if you restrict your AppBar color to background and surface colors, or their primary branded variants. In such a use case, you can use just one of the built-in style options, even when you use TabBars in the AppBar and on other surfaces.

tab bar on surface

True Black

Dark-mode is cool and with FlexColorScheme you can go even darker. Go true black with the flick of a switch. When using the true black option for dark-mode, surface, background and scaffold background are set to fully black. This can save power on OLED screens as the pixels are turned off, but it can also cause scrolling artefact issues when pixels turn fully on and off rapidly as you scroll. You can read about this and see an example of it in the Material design guide as well. Scroll back up one heading from the link to get to the mention of it.

If you use surface blends with true black mode enabled, you will notice that the surface blends have a lower impact, only at higher blend levels does it have a visible effect. This is by design to keep most surfaces totally or very close to black when true black is combined with surface blends. If you really want complete black for all surfaces and backgrounds, then avoid combining the true black mode with blended surfaces. On the other hand, it still makes a much darker theme than the normal dark theme, which can look nice. It may also eliminate the scrolling issue, since all background colored pixels are not fully off in the true black modes when you use higher blend levels.

Here is an example of a branded dark theme with true black OFF (default) and true black ON, when using high surface blend level with the Red red wine color scheme.

ColorScheme example 5c dark   ColorScheme example 5b dark
Comparing true black OFF (left) and ON (right), with the Red red wine scheme.

Here is another difference example with the Deep blue sea scheme, when using mid-level blends, and a primary-colored AppBar in dark-mode.

ColorScheme example 5 dark   ColorScheme example 5 true black
Comparing true black OFF (left) and ON (right), with the Deep blue sea scheme.

Themed System Navigation Bar in Android

The HomePage's build method, for this example, starts by wrapping the entire page content in an AnnotatedRegion with a SystemUiOverlayStyle value that we get from the static helper FlexColorScheme.themedSystemNavigationBar(context, ...).

Using it, we can get a system navigation bar on Android phones that follows the active theme's background color and theme mode. The system navigation bar will get updated as you select new themes, different background color branding style and strength, and toggle dark and light theme mode. Many Flutter applications neglect or forget to include this feature in their application.

    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: FlexColorScheme.themedSystemNavigationBar(
        context,
        systemNavBarStyle: widget.controller.sysNavBarStyle,
        useDivider: widget.controller.useSysNavDivider,
        opacity: widget.controller.sysNavBarOpacity,
      ),
     ),
     child: ....
   );

Above the systemNavBarStyle, useDivider and opacity are tied the theme controller in example 5. You can find the implementation in the repo here. Together with the option to remove the status bar scrim, you can have complete control over the look of the AppBar status bar, and the system navigation bar, as shown below:

AppBar StatusBar   SysNavBar Style
Changing Android system status bar and navigation bar.

The top status bar scrim toggle, system navigation bar divider and style only have an impact on Android builds. They do not have any functionality on the live Web example.

The static helper FlexColorScheme.themedSystemNavigationBar(context, ...) is designed to provide a convenience wrapper for a SystemUiOverlayStyle that works for screens that use and adhere to current theme mode colors. If your application use screens that do not follow the current theme, then just use SystemUiOverlayStyle directly in the annotated region for such screens to define their desired style. You can also make your own convenience wrapper function or even just a const value for it if you need to use a fixed style and design frequently.

Known issue:

The Flutter issue #100027 "Using systemNavigationBarDividerColor changes statusBarIconBrightness and systemNavigationBarIconBrightness on Android 11", affects the themedSystemNavigationBar feature. Two temporary changes were made to FlexColorScheme.themedSystemNavigationBar implementation:

  • The divider feature is disabled until the issue has been resolved.

  • There is a temporary workaround that manages to keep system icons from getting the wrong brightness on Android 11 by calling systemChrome twice. These temporary fixes will be reverted when the fix for the Flutter issue has reached the stable channel.

You can also use the FlexColorScheme.themedSystemNavigationBar to hide the top status icons if you are not using an app bar at all. This can be useful on a splash or on-boarding page. Example 5 contains three different examples, each with their own limitations, read more in example 5 source code comments on how it can be used, here what they look like. The last example, SplashPage 2 would be the ideal version, and it works well on some versions of Android, but it seems to fail on newer ones, so you may prefer 1b instead.

ColorScheme Splash pages

Transparent System Navigation Bar

FlexColorScheme V4 added full support for transparent system navigation bar for Android SDK >= 29 (Android 10). The support is added via the opacity property in FlexColorScheme.themedSystemNavigationBar. Use and support for the opacity value on the system navigation bar is supported starting from Flutter 2.5. This PR 28616 amends it a bit and limits its functionality to Android SDK >= 29.

Examples 1 to 4 do not use the transparent or themed system navigation bar feature, but example 5, the Themes Playground, the Copy Playground and the default example, the Hot Reload Playground apps do. If you build them on an Android device, you can see it working if you use a device with the appropriate Android API level that supports it.

If you build example 5, the Themes Playground on an Android device, you can test it with the playground. With it, you can modify the settings interactively, just as theme settings in the Playground app. Set it to a themed background color to try it with colors from active theme, use partially, and even a fully transparent Android system navigation. You can adjust the settings using the "Android System Navigation Bar" panel. These settings are not a part of the theme setup that can be copied, since the required setup is not in ThemeData. You will have to add support for this manually in your app, by adding it to your widget tree.

SysNav-1   SysNav-2   SysNav-3   SysNav-4
Android System Navigation Bar: Light theme mode white, primary blended ColoScheme.background themed, partially transparent, and fully transparent

Above, we see the themed and transparent system navigation bar in action, even when we use the old 3 button system navigation. It works with the two-button option as well, and even with the thin gesture navigation bar.

It is just not as visible or obvious when using the thin gesture-based navigation bar. The example screenshots presenting all the built-in themes in Scheme reference were produced with a fully transparent system navigation bar, using the gesture mode navigation bar.