Design Hacking Flutter HCT Color Space
Putting brand colors back into HCT the design way because Google was too damn lazy.
So let me explain it this way. With MD2, we had color swatches that were manually adjusted from the HSV color space. Which made it damn difficult for incorporating brand colors and Google threw up their hands and said use copyWith which did nothing to solve the problem of brand colors incorporating with the color scheme. Here we get HCT which is a mash-up of two color spaces and again Google throws up their hands and says just use copyWith!
Yes, I crafted a design solution, as most of my current audience members expect such design tricks from me.
Fact of job hunting for front-end designers, code terms get the recruiter's attention, but actual design gets you hired. Does it make sense to read Flutter GDE content where they are asking to use legacy UseCase, Repository, and Datasource Class abstraction when no Flutter app designer uses that crap anymore?
I source top design tricks and techniques from the top presenters at Google IO such as GSkinner, VeryGoodVentures, RydMike, and many others to make sure you have the top flutter app design tips and tricks to make your flutter app design awesome. Because, design and UX is the damn point!
Why not join Miguel Angel Toscan, Roman Roan, etc. in sharing this post?
Some HSV and HCT Background
With Material Design 2 we had manually adjusted color swatches that were in the HSV color space. They were manually adjusted to correct for color contrast.
And with Color Scheme in MD2, Google implied that copyWith brand colors into HSV was enough. But most of us noticed that definition between brand color relationships was lost when you did that it looked like shit when you viewed the overall full colors of the generated color scheme.
Now, I am not explaining the math as I will lose most of you. But, math-wise mashing CIE-lab and Cam16 color together into one color space means that the palettes can be arranged into color roles and there is no manual adjusting to meet contrast requirements.
However, how do we incorporate RGB brand colors into this HCT generated color scheme? Google threw up their hands and gave the pat answer of copyWith. Whereas, I dug in and found a way.
So I just re-worked the dynamic color example to show this new design hack using one brand color.
An Example
Okay, this is the brand color I used:
And the screenshot is:
All the magic happens in the MyApp class:
// Copyright 2024 Fredrick Allan Grott. All rights reserved. | |
// Use of this source code is governed by a BSD-style | |
// license that can be found in the LICENSE file. | |
import 'package:dynamic_color_sdk/my_home_page.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:dynamic_color/dynamic_color.dart'; | |
import 'package:material_color_utilities/blend/blend.dart'; | |
const brandBlue = Color(0xFF1E88E5); | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
// This widget is the root of your application. | |
@override | |
Widget build(BuildContext context) { | |
return DynamicColorBuilder(builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { | |
ColorScheme lightColorScheme; | |
ColorScheme darkColorScheme; | |
Color lightPrimarySource; | |
Color lightPrimaryOutput; | |
Color darkPrimarySource; | |
Color darkPrimaryOutput; | |
// android, fuschia, desktop | |
if (lightDynamic != null && darkDynamic != null) { | |
// On Android S+ devices, use the provided dynamic color scheme. | |
// (Recommended) Harmonize the dynamic color scheme' built-in semantic colors. | |
// shifts hue of error towards primary | |
lightColorScheme = lightDynamic.harmonized(); | |
// grab primary | |
lightPrimarySource = lightColorScheme.primary; | |
// use brand color to shift hue towards brand | |
lightPrimaryOutput = Color(Blend.harmonize(brandBlue.value, lightPrimarySource.value)); | |
// copyWith back into the computer color scheme | |
lightColorScheme.copyWith(primary: lightPrimaryOutput); | |
// repeat for dark | |
darkColorScheme = darkDynamic.harmonized(); | |
darkPrimarySource = darkColorScheme.primary; | |
darkPrimaryOutput = Color(Blend.harmonize(brandBlue.value, darkPrimarySource.value)); | |
darkColorScheme.copyWith(primary: darkPrimaryOutput); | |
} else { | |
// for the platforms that do not have dynamic color such as iOS and web | |
lightColorScheme = ColorScheme.fromSeed( | |
seedColor: brandBlue, | |
); | |
// now we do the hue shift of our brand color | |
lightPrimarySource = lightColorScheme.primary; | |
lightPrimaryOutput = Color(Blend.harmonize(brandBlue.value, lightPrimarySource.value)); | |
lightColorScheme.copyWith(primary: lightPrimaryOutput); | |
darkColorScheme = ColorScheme.fromSeed( | |
seedColor: brandBlue, | |
brightness: Brightness.dark, | |
); | |
darkPrimarySource = darkColorScheme.primary; | |
darkPrimaryOutput = Color(Blend.harmonize(brandBlue.value, darkPrimarySource.value)); | |
darkColorScheme.copyWith(primary: darkPrimaryOutput); | |
} | |
return MaterialApp( | |
debugShowCheckedModeBanner: false, | |
title: 'Flutter Demo', | |
theme: ThemeData( | |
colorScheme: lightColorScheme, | |
useMaterial3: true, | |
), | |
darkTheme: ThemeData( | |
colorScheme: darkColorScheme, | |
brightness: Brightness.dark, | |
), | |
home: const MyHomePage(title: 'Flutter Demo Home Page'), | |
); | |
}); | |
} | |
} |
I am using the Material Color Utilities package Blend.harmonize method to shift the hue of the primary of color scheme towards the hue of the brand color, then I copyWith back into the color scheme.
Obviously, my soft fork of dynamik themes now has to change as I have to be able to pass in the brand colors.
What is left design wise whether also to do the same treatment with the color role containers. That might depend upon whether you have one brand color or two or more.
Also, should I shift the color scheme that is not dynamic colorized as far as the error color roles and its containers? It's a design nuance that Google missed, as the dynamicColorScheme.harmonize method does that in the dynamic ColorScheme if block.
More work to do in my soft fork of dynamik themes in updating for this in using just SDK color scheme generation and the flex color scheme way.
It's not just flutter app design tricks and tips I am curating for this flutter newsletter audience. There is a collection of must-have plugins that make our flutter app designs and UX more powerful in the eyes of our app users.
And, not every plugin is endorsed by Google, like go-router. I am talking about using Flex Color Scheme to better customize the Color Scheme generation and generate the component themes as just one branding empowerment example. It's all about improving the UX and design while at the same time giving them the device-platform experience they are accustomed to on their device.
Why not join Roman Roan, Miguel Angel Toscan, etc. in subscribing today.