Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

theme_data largely ignores primarySwatch when brightness is set to dark #19089

Closed
InsanityOnABun opened this issue Jul 5, 2018 · 22 comments
Closed
Labels
customer: crowd Affects or could affect many people, though not necessarily a specific customer. f: material design flutter/packages/flutter/material repository. found in release: 1.21 Found to occur in 1.21 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P2 Important issues not at the top of the work list

Comments

@InsanityOnABun
Copy link

InsanityOnABun commented Jul 5, 2018

primaryColor ??= isDark ? Colors.grey[900] : primarySwatch;

primaryColorLight ??= isDark ? Colors.grey[500] : primarySwatch[100];

primaryColorDark ??= isDark ? Colors.black : primarySwatch[700];

toggleableActiveColor ??= isDark ? Colors.tealAccent[200] : (accentColor ?? primarySwatch[600]);

accentColor ??= isDark ? Colors.tealAccent[200] : primarySwatch[500];

// Spec doesn't specify a dark theme secondaryHeaderColor, this is a guess.
secondaryHeaderColor ??= isDark ? Colors.grey[700] : primarySwatch[50];

This is just a small selection of examples of the dark theme having values set to greyscale or teal (why in the world teal?) values, ignoring the primarySwatch. This caused a lot of theming confusion for me at first, especially with some colors inexplicably being set to teal.

Expected behavior would be for theme_data to properly use the selected primarySwatch, regardless of light or dark brightness.

@zoechi zoechi added the f: material design flutter/packages/flutter/material repository. label Jul 5, 2018
@zoechi zoechi added the framework flutter/packages/flutter repository. See also f: labels. label Jul 22, 2018
@johrpan
Copy link

johrpan commented Aug 2, 2018

This is something that confused me too. Is that done on purpose? And just for clarification (@InsanityOnABun already said it implicitly): This also applies to accentColor, not only primarySwatch.

@jdkoren
Copy link

jdkoren commented Dec 7, 2018

I ran into this as well. Ideally, one should be able to create reasonable themes using only

new ThemeData(brightness: Brightness.light, primarySwatch: Colors.red); // light theme
new ThemeData(brightness: Brightness.dark, primarySwatch: Colors.red); // dark theme

@zoechi zoechi added this to the Goals milestone Dec 9, 2018
@gerenook
Copy link

Any update on this?

@Flexicon
Copy link

Flexicon commented Apr 7, 2019

Bumping this issue. Is this really intended?

@sidevesh
Copy link

This is a really weird issue! Can anyone explain the motivation behind this ? Was stuck on this for hours and was only able to figure this out by going through flutter source.

@sidevesh
Copy link

      darkTheme: ThemeData(
        brightness: Brightness.dark,
        fontFamily: 'Raleway',
        primarySwatch: Colors.orange,
        accentColor: Colors.orange[500],
        toggleableActiveColor: Colors.orange[500],
        textSelectionColor: Colors.orange[200],
      ),

For anyone else encountering this issue, I used the above properties to get dark theme with correct colors for my app (calculations are taken from the flutter source).

@TahaTesser TahaTesser added the customer: crowd Affects or could affect many people, though not necessarily a specific customer. label Feb 12, 2020
@kf6gpe kf6gpe added the P2 Important issues not at the top of the work list label May 29, 2020
@markusaksli-nc
Copy link
Member

Reproducible on latest master 1.21.0-2.0.pre.55.

Minimal reproducible sample
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
 runApp(Wrapper());
}

class Wrapper extends StatefulWidget {
 static _WrapperState instance;
 @override
 _WrapperState createState() {
   instance = _WrapperState();
   return instance;
 }
}

class _WrapperState extends State<Wrapper> {
 Brightness _brightness = Brightness.light;
 @override
 Widget build(BuildContext context) {
   final appName = 'Custom Themes';

   return MaterialApp(
     title: appName,
     theme: ThemeData(
       // Define the default brightness and colors.
       brightness: _brightness,
       primarySwatch: Colors.red,

       // Define the default font family.
       fontFamily: 'Georgia',

       // Define the default TextTheme. Use this to specify the default
       // text styling for headlines, titles, bodies of text, and more.
       textTheme: TextTheme(
         headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
         headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic),
         bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),
       ),
     ),
     home: MyHomePage(
       title: appName,
     ),
   );
 }
 void update(){
   setState(() {
     _brightness = _brightness == Brightness.dark ? Brightness.light : Brightness.dark;
   });
 }
}

class MyHomePage extends StatelessWidget {
 final String title;

 MyHomePage({Key key, @required this.title}) : super(key: key);

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text(title),
     ),
     body: Center(
       child: Container(
         color: Theme.of(context).accentColor,
         child: Text(
           'Text with a background color',
           style: Theme.of(context).textTheme.headline6,
         ),
       ),
     ),
     floatingActionButton: FloatingActionButton(
       onPressed: (){
         Wrapper.instance.update();
       },
       child: Icon(Icons.wb_sunny),
     ),
   );
 }
}
flutter doctor -v
[√] Flutter (Channel master, 1.21.0-2.0.pre.55, on Microsoft Windows [Version 10.0.18362.900], locale et-EE)
   • Flutter version 1.21.0-2.0.pre.55 at C:\Development\flutter_master
   • Framework revision 1840b7121a (4 hours ago), 2020-07-16 23:15:23 -0700
   • Engine revision d3b81f19fc
   • Dart version 2.9.0 (build 2.9.0-21.0.dev 06183779d7)


[√] Android toolchain - develop for Android devices (Android SDK version 30.0.0)
   • Android SDK at C:\Users\Isa\AppData\Local\Android\sdk
   • Platform android-30, build-tools 30.0.0
   • Java binary at: C:\Users\Isa\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-0\193.6626763\jre\bin\java
   • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
   • All Android licenses accepted.

[√] Chrome - develop for the web
   • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

[√] Android Studio (version 4.0)
   • Android Studio at C:\Users\Isa\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-0\193.6626763
   • Flutter plugin version 47.1.2
   • Dart plugin version 193.7361
   • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)

[√] Connected device (4 available)
   • SM G950F (mobile)       • ce12171c51cc001c03 • android-arm64  • Android 9 (API 28)
   • sdk gphone x86 (mobile) • emulator-5554      • android-x86    • Android 11 (API 30) (emulator)
   • Web Server (web)        • web-server         • web-javascript • Flutter Tools
   • Chrome (web)            • chrome             • web-javascript • Google Chrome 84.0.4147.89

• No issues found!

@markusaksli-nc markusaksli-nc added found in release: 1.21 Found to occur in 1.21 has reproducible steps The issue has been confirmed reproducible and is ready to work on labels Jul 17, 2020
@Hixie Hixie removed this from the None. milestone Aug 17, 2020
@bartekpacia
Copy link
Member

any progress? this makes implementing a properly looking dark mode much harder

@andyngdz
Copy link

I'm getting the same issue:

import 'package:flutter/material.dart';

ThemeData themeData(BuildContext context) {
  ThemeData themeData = ThemeData(
    visualDensity: VisualDensity.adaptivePlatformDensity,
    primarySwatch: Colors.orange,
    brightness: Brightness.dark,
  );

  return themeData;
}

I expect the color will be orange but it's tea instead.

@lukepighetti
Copy link

lukepighetti commented Feb 21, 2021

Due to ThemeData being highly opinionated and also not exposing the MaterialColor primarySwatch to descendants, we design custom widgets using colorScheme.

Our users select a theme color, such as red, but when in dark mode it switches to teal. The current behavior is very difficult to reason with. I see nothing in material spec that says the only color allowed in dark mode is teal. @willlarche

In another thread #26323 (comment) it was said that this can't be changed because of downstream goldens. I would argue that this is not a valid reason to keep this behavior since the solution for people with broken goldens would be to make their dark theme primarySwatch teal. The friction to fix goldens is much lower than the friction to build new apps with the current behavior. @HansMuller

Net benefit is strongly in favor of fixing this behavior to ensure that dark mode color matches the theme color.

@willlarche
Copy link
Member

PrimarySwatch is a vestigial api from earlier versions of this library before Material Theming had a proper spec. It was a great way to get a headstart on theming but it now operates only as it was written many years ago and there is no requirements document to update it to.

@HansMuller can we consider deprecating this API?

@miDeb
Copy link
Contributor

miDeb commented Feb 21, 2021

I think it makes sense to provide an easy way to change the main color of an app. Even the current default counter app advertises primarySwatch and encourages to change it and see what happens. I see zero reason to deprecate this API given its high exposure in official samples and usefulness (currently in light mode only :). It should rather be fixed to play well with dark mode IMHO.
What would the alternative to primarySwatch be?

@lukepighetti
Copy link

lukepighetti commented Feb 21, 2021

@willlarche color is an area of Material that I feel like I could spend months trying to unravel. I'd love to connect with you offline to try to better understand how to align the Flutter APIs to material spec as I think they are missing the mark at the moment. It's almost impossible to figure out what widgets use which part of ThemeData and a lot of widgets are applying their own colors. We have multiple ways to get color systems and none of them are complete, and all of them have weird behaviors like the one covered by this issue.

Right now we can specify a primary swatch and broad-brush theme an app to a specific brand color. It works pretty well. Without that, I'm not sure what's left for simple theming? I'd love to get your ideas for a new MaterialTheme class that is very succinct and flexible. Then, I suspect, we can deprecate the existing Material color apis and bring consistency and clarity to design/build teams.

Is this the goal?

Screen Shot 2021-02-21 at 12 09 25 PM

from https://material.io/design/color/the-color-system.html

I believe this is covered by ColorScheme which has the properties below. This is what we try to use (and in regards to this issue, the primary and secondary is always teal in dark mode).

Color primary
Color primaryVariant
Color secondary
Color secondaryVariant
Color surface
Color background
Color error
Color onPrimary
Color onSecondary
Color onSurface
Color onBackground
Color onError
Brightness brightness

@willlarche
Copy link
Member

@lukepighetti Material Theming is indeed not perfect (in spec or any platform) and we hope to continue improving it in the future at the requirements level.

In regards to Flutter's implementation, yes, @HansMuller was just reminding me that we have debt for completing the integration of ColorScheme throughout the system and we're going to look into what that entails. He's done a great job getting it in some of the hardest places and we'll see about the rest of it.

For this particular issue, I agree that Teal is not ideal. We could help come up with another solution but it would not be based on official Material Theming spec. I still think it's worth figuring out how to remove PrimarySwatch (and @miDeb removing it from code examples as well) but I totally defer to @HansMuller . Hans, what you thinking?

@ptrckdev

This comment has been minimized.

@darrenaustin
Copy link
Contributor

Our long term plan for cleaning up the use of color is documented in:

https://flutter.dev/go/material-theme-system-updates

And tracked in #91772.

We have made a lot of progress migrating components to use ColorScheme colors for their defaults, but there is still obviously a lot of work to do. Going forward, we are likely to phase out the primarySwatch, as that concept is no longer part of Material Design.

For Material 3 we have added the ability to generate a full ColorScheme (either light or dark) from a single seed color, which will hopefully help replace the primarySwatch. In addition, as we have been migrating components to Material 3, they will all use ColorScheme colors by default. You can track this work in #91605.

As this is being addressed in the mentioned issues, I am going to close this issue.

@lukepighetti
Copy link

lukepighetti commented Apr 21, 2022

I believe that a new user should be able to get a reasonable dark mode experience with:

ThemeData(
  brightness: Brightness.dark,
  primarySwatch: Colors.red,
),

Let's compare this with light mode and dark mode on current master branch 2.13.0-0.0.pre.656 rev c910d8d58d

light mode with expected red colors dark mode with unexpected teal colors

As you can see, dark mode is using an unexpected teal color. It has been this way since the beginning, and continues to be this way.

The expected result is for the red color to be used.

This issue is not resolved and I would urge you to reopen it until it is cc @darrenaustin

@lukepighetti
Copy link

lukepighetti commented Apr 21, 2022

One possible solution is to deprecate primarySwatch and redirect developers to use colorSchemeSeed, but that requires folks to buy into Material3, which is a whole matter of taste IMHO, since you end up with a muted pink instead of red

ThemeData(
  brightness: Brightness.dark,
  colorSchemeSeed: Colors.red,
),

Current master branch 2.13.0-0.0.pre.656 rev c910d8d58d

material3 light mode with muted red colors material3 dark mode with muted red colors

@rydmike
Copy link
Contributor

rydmike commented Apr 22, 2022

Roads to ThemeData

Yeah this is a bit tricky, I agree @lukepighetti. I have been a proponent for simpler ways for users to get a ThemeData with more expected results for years too.

It will get a bit better, once all of this #91772, is ready and lands, plus all the new M3 theming ways, but even all the new ways to make ThemeData is not solving all the issues. It might also cause even more confusion if all the old swatch based methods remain.

Any method that is MaterialColor swatch based produces a dark theme that is more grey then a real dark theme. Basically some old improvised dark mode, made before the Material 2 dark mode was speced. Any old ThemeData creation method that creates ThemeData from a swatch, like providing a primarySwatch or using a ColorScheme made using ColorScheme.fromSwatch, which btw is a complete mess when it comes to the result, should really be deprecated so users understand to avoid them.

You have to either create the ThemeData with a full ColorScheme that includes more correct background/surface colors, or use the ThemeData.from a ColorScheme that gives you almost an expected ThemeData. Still, just using a ColorScheme even if it has correct surface and background colors, in a plain ThemeData is not enough, you will still get grey dark mode, unless you assign colors from your ColorScheme to still important ThemeData colors.

Below a few comparisons to highlight the issues.

ThemeData(colorSchemeSeed)

Making ThemeData from a seed color like you did above is nice, if you go for the M3 style. However, this particular method that you used, even if very new, is also a bit flawed, not only for your mentioned reason.

      title: '7) TD colorSchemeSeed',
      theme: ThemeData(
        brightness: Brightness.light,
        colorSchemeSeed: Colors.red,
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        colorSchemeSeed: Colors.red,
      ),

But also because it assigns the M3 seed generated outline color directly to ThemeData.dividerColor without any opacity, resulting in really funky looking theme if you have used ThemeData.dividerColor. You can still use it, if you fix it of course.
The divider color is not specifically mentioned in the M3 web guide, the outline color and divider is not the same though, we can see that in the M3 guide that contains dividers in some menus. Probably divider should be outline with some opacity/alpha.

material3 colorSchemeSeed light material3 colorSchemeSeed dark

And yes, at least in stable you still have that teal color on toggleableActiveColor so you would get teal switches among other things. This #95870 will hopefully fix some of that, but probably not completely if does not break a few things along the way too.

ThemeData.from

You can get a much better looking theme if you create it with the ThemeData.from a ColorScheme factory. If you then use a custom fully specified ColorScheme, or from the ColorScheme.light/dark factories using overrides, or one from a seed using ColorScheme.fromSeed depends on what you want. Let's compare ThemeData.from to the above same
Colors.red (effectively Colors.red[500]), using it as key color for a seeded ColorScheme.fromSeed:

   title: '9) TD.from fromSeed',
   theme: ThemeData.from(
       colorScheme: ColorScheme.fromSeed(
         seedColor: Colors.red,
         brightness: Brightness.light,
       ),
     ),
     darkTheme: ThemeData.from(
       colorScheme: ColorScheme.fromSeed(
         seedColor: Colors.red,
         brightness: Brightness.dark,
       ),
     ),

We then get this:

material3 colorSchemeSeed light material3 colorSchemeSeed dark

No problem with the ThemeData.dividerColor in this case. Plus the light theme is almost perfect, except that it has ThemeData colors named primaryColorLight and primaryColorDark as well as secondaryHeaderColor in ThemeData that are now blue, and have nothing to do with the primary color in the colors we defined. One might argue that this is "a bit" confusing. Fortunately at least the toggleableActiveColor is matching our secondary color, so that will yield expected results on switches etc.

In dark mode, things are worse though, the teal color is still there on toggleableActiveColor and our primaryColor, primaryColorLight and primaryColorDark in ThemeData are now all grey, which again has nothing at all to do with our defined primary color we wanted, even more confusing, one might argue.

Some might correctly point out that we have the deprecation of ThemeData colors via https://github.com/flutter/flutter/issues/91772 in progress, yes right, but those colors are not mentioned in it, which I commented on here #91772 (comment).

ThemeData(colorScheme: ColorScheme.fromSeed)

Let's then try ThemeData(colorScheme: ColorScheme.fromSeed), one might think that the above one would result in same things as doing this:

      title: '8) TD scheme.fromSeed',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.red,
          brightness: Brightness.light,
        ),
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.red,
          brightness: Brightness.dark,
        ),
      ),

But the above gets you a worse theme in dark mode that is more grey (pre M2 dark mode) and with none of the critical surface, card, canvas, background, sacffoldBackground and error colors from the ColorScheme actually applied to ThemeData colors in light or dark mode. Many widgets still use those ThemeData colors (in stable), so what your app actually looks like might be even more surprising:

material3 colorSchemeSeed light material3 colorSchemeSeed dark

Only conclusion can be, don't do this!

Study of Roads to ThemeData

I did a ThemeData study, as general guidance in the docs for the FlexColorScheme package. It is more of an investigation of current state of different ways to make a ThemeData object with plain vanilla Flutter and comparing the different results, sure I compare them to FlexColorScheme package results too.

I used different colors than here, but these were the different ways to make ThemeData that I looked at, compared and analyzed:

  1. ThemeData.light/dark() and ThemeData(brightness: Brightness.light/dark)
  2. ThemeData(primarySwatch: ...)
  3. ThemeData(colorScheme: ColorScheme.fromSwatch(...))
  4. ThemeData.from(colorScheme: ColorScheme.fromSwatch(...))
  5. ThemeData(colorScheme: ColorScheme(...))
  6. ThemeData.from(colorScheme: ColorScheme(...))
  7. ThemeData(colorSchemeSeed: Color(...)),
  8. ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Color(...))),
  9. ThemeData.from(colorScheme: ColorScheme.fromSeed(seedColor: Color(...))),
  10. FlexThemeData.light/dark(colorScheme)
  11. FlexThemeData.light/dark(colorScheme, keyColors)
  12. FlexThemeData.light/dark(colorScheme, keyColors, subThemesData)

The numbers I used on the screenshots above, use the same numbers as above list, and in the study.
It is available here: https://docs.flexcolorscheme.com/theming_roads

What to do and use?

Setting it up with the same used red example as above, we could do eg something like further below to get a ThemeData that is consistent and contains no surprises.

To start we need to define a ColorScheme. Using ColorScheme.fromSeed is a good starting point, we can also "lock" a few colors down in it, to some given colors we want, say they are our brand colors are the Colors.red[600] and Colors.red[900], that we must have in light mode. We can then just override those in the fromSeed.

This works well as long as its needed onColor, is same as the one computed fromSeed generates for the color that we replace, if it is not, we have to assign the onColor too. Here I use colors from the Colors.red swatch that meet that criteria.

final ColorScheme redSchemeLight = ColorScheme.fromSeed(
  brightness: Brightness.light,
  seedColor: Colors.red,
  primary: Colors.red[600],
  secondary: Colors.red[900],
);

final ColorScheme redSchemeDark = ColorScheme.fromSeed(
  brightness: Brightness.dark,
  seedColor: Colors.red,
  primary: Colors.red[200],
  secondary: Colors.red[100],
);

For locking down the primary and secondary colors like above to also works well with all the computed colors produced by ColorScheme.fromSeed, they must have a color and shade reasonably close to the color we used as our seed color.

The fromSeed algorithm uses the hue in the seed color for both primary and secondary tonal palette computation that it picks the colors from, to the ColorScheme. Primary just has a min chroma of 48, of the hue in the seed color, and secondary is fixed to 16, making the secondary tonal palette use the same color base as the seed, but with low chroma, so the the produced tonal palette is typically is very muted/pastel like.

If you want to use a color for secondary that is not using the same color base like your seed color, then it does not work so well if you try to set any other none red-like color as a fixed brand color for the secondarycolor, when using a red like color as seed color.

This is the case with tertiary color in the fromSeed too, it uses the input seed color to generate it, but rotates it 60 degrees or something like that. So you would be stuck to using something reasonably close to the yellow/green color you get with seeded scheme for it, in order for it to fit with other colors is computes. Alternatively replace them all, same with secondary if you want to use something that is not related to the red seed color used for it.

A bit of shameless plug, with FlexColorScheme you can also use a given seed color for secondary and tertiary tonal palette calculation, making it possible to get computed colors that match possible brand colors you want to lock down as main color of them too. Plus you can change the min chroma levels used on primary, secondary and tertiary, and you can even change the palette to ColorScheme color mapping.

This is what the "light" generated ColorsScheme.fromSeed(seedColor: Colors.red) looks like, without any colors replaced in it:

image

And here is the dark one:

image

The ToneXX above refers to which tone each color in the ColorScheme was assigned a color from, using the "Core Palettes" tonal palettes produced by the ColorScheme.fromSeed, which for Colors.red produces these tonal palettes in order

  • primary tonal palette
  • secondary tonal palette
  • tertiarytonal palette
  • error tonal palette
  • neutral tonal palette
  • neutralVariant tonal palette

image

You should always use same color as input to your light and dark theme mode fromSeed. The algo then picks different tones, as shown above, from the identical tonal palettes for light and dark mode ColorSchemeit makes, depending on the brightness provided to fromSeed.

Fixed ThemeData

Sorry got a bit side tracked, seeded ColorSchemes can be fascinating, back to making the ThemeData with the red case.

We could then use the ThemeData.from a ColorScheme with the above ColorSchemes, but since we know it requires a lot of fixes, we would have to add those with an extra copyWith, so we might as well use the plain ThemeData factory, and do all the things the ThemeData.from factory actually does right. We skip setting the deprecated props it still sets, and add a few minor fixes to it that Flutter SDK has neglected for years. Here is one apporach to that:

      title: 'Fixed ThemeData',
      theme: ThemeData(
        colorScheme: redSchemeLight,
        primaryColor: redSchemeLight.primary,
        primaryColorLight: Color.alphaBlend(
            Colors.white.withAlpha(0x66), redSchemeLight.primary),
        primaryColorDark: Color.alphaBlend(
            Colors.black.withAlpha(0x66), redSchemeLight.primary),
        secondaryHeaderColor: Color.alphaBlend(
            Colors.white.withAlpha(0xCC), redSchemeLight.primary),
        toggleableActiveColor: redSchemeLight.secondary,
        scaffoldBackgroundColor: redSchemeLight.background,
        canvasColor: redSchemeLight.background,
        backgroundColor: redSchemeLight.background,
        cardColor: redSchemeLight.surface,
        bottomAppBarColor: redSchemeLight.surface,
        dialogBackgroundColor: redSchemeLight.surface,
        indicatorColor: redSchemeLight.onPrimary,
        dividerColor: redSchemeLight.onSurface.withOpacity(0.12),
        errorColor: redSchemeLight.error,
        applyElevationOverlayColor: false,
      ),
      darkTheme: ThemeData(
        colorScheme: redSchemeDark,
        primaryColor: redSchemeDark.primary,
        primaryColorLight: Color.alphaBlend(
            Colors.white.withAlpha(0x59), redSchemeDark.primary),
        primaryColorDark: Color.alphaBlend(
            Colors.black.withAlpha(0x72), redSchemeDark.primary),
        secondaryHeaderColor: Color.alphaBlend(
            Colors.black.withAlpha(0x99), redSchemeDark.primary),
        toggleableActiveColor: redSchemeDark.secondary,
        scaffoldBackgroundColor: redSchemeDark.background,
        canvasColor: redSchemeDark.background,
        backgroundColor: redSchemeDark.background,
        cardColor: redSchemeDark.surface,
        bottomAppBarColor: redSchemeDark.surface,
        dialogBackgroundColor: redSchemeDark.surface,
        indicatorColor: redSchemeDark.primary,
        dividerColor: redSchemeDark.onSurface.withOpacity(0.12),
        errorColor: redSchemeDark.error,
        applyElevationOverlayColor: true,
      ),

Above we could of course also use colors from the Colors.red swatch as input on those old primary related colors in ThemeData, but the above computed versions works well also if we don't have such colors and only want to base it off the ColorScheme we made.

The resulting theme colors then looks like this:

material3 custom light material3 custom dark

Which no longer contains any surprises and we spliced in some locked down "brand" red colors into the frey of colors ColorScheme.fromSeed computes too. Sure we can define all those colors by hand too, but it is rather tedious and the algorithm produces quite well balanced colors. As long as we can get our own "brand" colors locked in, typically on at least primary and maybe also secondary that might work well enough for most use cases, imo it does so.

If using seed colors for all extra colors are not a desired thing, then making the ColorSchemes above with plain ColorScheme.light() and ColorScheme.dark and overriding at least primary, secondary and tertiary to desired colors, obviously also works well for the input ColorSchemes above too. Just check their onColors if the used colors needs a different one than the default he factory gives you.


Well this is my take on this situation and solution approach with just plain ThemeData.

I've kind of given up on trying to convince anybody that it should be more consistent, more like this out of the box. Arguing that it is confusing to new comers and even us Flutter old timers in its current state, seems a bit futile. It will likely remain confusing going forward too.

As shown one can fix it, make the theme work as desired, but it takes a bit of an effort and knowing what you need to do to get a ThemeData that contains no color surprises and that covers all widgets so they get themed accordingly as well.

I know writing all of this here is seen by very few, but perhaps it helps somebody, plus I might use it in a blog later too 😄

@lukepighetti
Copy link

Well, that was incredible. And I hope you don't mind me saying that if the question of color in dark theme inspires a post of that magnitude, it means someone probably needs to take a serious look at how things are architected in the world of color in ThemeData

@rydmike
Copy link
Contributor

rydmike commented Apr 22, 2022

Thanks, being "quite" familiar with all nuances of Colors in Flutter themes, I don't really notice any problems with it for my own needs. However, looking at it from the perspective of somebody new to Flutter, trying to get a grip of all the ways to make a color correct ThemeData object for your app's theme. I can see how it may be both confusing and frustrating.

Lot's of similar issues posted here about it. I also frequently see apps and Flutter code that defines the theme for their apps in ways that end up producing poor dark theme and missing a color here and there in light mode too, and the eternal teal color of course. Then they instead end up adjusting colors on widget level, outside of their theme, just to get the job done and can't figure out why they did not get "red", like above, if that was asked for.

I know you grok all this too @lukepighetti, but it has to be a pain if you are new to Flutter. Why the colors are the way they are is a lot of legacy stuff. One can of course just read all code of ThemeData and ColorScheme and it becomes obvious at least then why the colors in them get the colors they do using the different methods to create your ThemeData object.

This of course still does not give you any clue or insights into which component and element will end up using what color by default, since there is no documentation of that anywhere to be found. Best option is, fork/clone the repo and search for a theme color of interest in the entire repo and find all places it is referenced :)

Full M3 ColorScheme's that actually follow the M3 Color System, will likely confuse many even more, add seeded versions with brand overrides to the mix and it's a party. And I did not even mention using dynamic theme from A12 where you then adjust/tune your custom colors to fit better with the colors in the dynamic theme, which is quite interesting of course.

Many will just be "I want my theme red", and get confused when it it is not, when they think they defined that in their theme.

iOS world is quite a bit simpler, there is pretty much only "primary" color, and AppBar is often just white in light mode too, so you just go "blue" and buttons, selected items, small topic headings , etc are "blue" and that's it. Well OK there are the classic iOS green switches, but at least they are so because they are speced that they should be so, it is not a surprise, it is actually expected.

@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
customer: crowd Affects or could affect many people, though not necessarily a specific customer. f: material design flutter/packages/flutter/material repository. found in release: 1.21 Found to occur in 1.21 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P2 Important issues not at the top of the work list
Projects
None yet
Development

No branches or pull requests