components icon indicating copy to clipboard operation
components copied to clipboard

feat(theme-generator): Add possibility to force custom color @angular/material:theme-color

Open zygarios opened this issue 1 year ago • 20 comments

Feature Description

My problem is that I still can't understand how @angular/material:theme-color works. In the terminal, I need to provide colors for primary, secondary, and tertiary. For each one, I specify a single color, like #ff9500, because that's what I want.

What HEX color should be used to generate the M3 theme? It will represent your primary color palette. (ex. #ffffff) **#005dba** What HEX color should be used represent the secondary color palette? (Leave blank to use generated colors from Material) **#005dba** What HEX color should be used represent the tertiary color palette? (Leave blank to use generated colors from Material) **#005dba**

However, when generating the theme, I see that it creates color palettes, but when I try to get just my color, I use --mat-sys-primary. Unfortunately, it appears significantly darker (#8c5000), and I don't know which variable the color is actually hidden under. I have the impression that due to the low contrast, it is being artificially darkened. However, I selected the option not to generate mixins with high contrast. I would like to be able to use exactly that color without worrying about contrast. Currently, I am forced to manually change all the system token colors because the generated palette still interferes too much with what I want to achieve, taking away the flexibility I need in this case.

Use Case

It would be great to be able to specify in the terminal that I want exactly the colors I mention, without any interference.

zygarios avatar Dec 11 '24 17:12 zygarios

Very big +1. The fact that you cannot provide exact colors to an M3 theme is a deal breaker for me. Not being able to use my company's brand colors just because they aren't "up to Material's standards" just feels wrong.

I know I can manually override every color variable, and I will do so when support for M2 gets dropped eventually, but this is just very annoying and I really hope there will be an easier way to use exact colors for a theme.

jimivdw avatar Dec 13 '24 08:12 jimivdw

@zygarios For each color specified, those colors are used directly to create color palettes of tones that match that color's hue and chroma. If you only specify a single color or a subset, the other colors get created in relation to each other. So there shouldn't be much adjusting happening if you specify everything and aren't requesting high contrast.

Although, Material defines --mat-sys-primary as tone 40 in your primary palette. Typically the color used to create the color palette usually closest to tone 50 in the color palette, which is why it is slightly off to what you are expecting.

Although I do see what you are saying, it would be ideal if you can specify maybe the primary color and then the color palette gets extracted from that to get more color accuracy and be more intuitive. I'll leave this issue open so this can be looked into.

In the meantime, overriding colors is your best option for complete customization: https://material.angular.io/guide/theming#system-tokens

amysorto avatar Dec 16 '24 16:12 amysorto

This is a very blocking issue right now. It's beautifull having all kinds of colors, but our customers want at least a toolbar with their brand color.

An example (a very ugly primary color but just to let you see the difference): Image

in the new M3 Image

@amysorto overriding the colors isn't the best option, because we don't know the text contrast colors

An example for changing the mat-toolbar

@use '@angular/material' as mat;
:root {
  @include mat.toolbar-overrides((
    container-background-color: var(--primary-color), // instead of using a palette color, since the brand collor is not within the palette
    container-text-color: var(--mat-sys-on-primary), // then we don't know what the contrast color of var(--primary-color) is
  ));
}

we should have someting like this https://github.com/angular/components/issues/30070 so we can use

@use '@angular/material' as mat;
:root {
  @include mat.toolbar-overrides((
    container-background-color: var(--mat-sys-primary-40),
    container-text-color: var(--mat-sys-on-primary-40), 
  ));
}

But then I'm still missing my brand color

richardsengers avatar Jan 07 '25 15:01 richardsengers

For those interested, I've created a small function which sets all theme tones to css vars, which you can use in your own color palette

import { argbFromHex, hexFromArgb, themeFromSourceColor } from '@material/material-color-utilities';

const hueTones = [0, 10, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100];
// Map of neutral hues to the previous/next hues that
// can be used to estimate them, in case they're missing.
const neutralHues = new Map<number, { prev: number; next: number }>([
  [4, { prev: 0, next: 10 }],
  [6, { prev: 0, next: 10 }],
  [12, { prev: 10, next: 20 }],
  [17, { prev: 10, next: 20 }],
  [22, { prev: 20, next: 25 }],
  [24, { prev: 20, next: 25 }],
  [87, { prev: 80, next: 90 }],
  [92, { prev: 90, next: 95 }],
  [94, { prev: 90, next: 95 }],
  [96, { prev: 95, next: 98 }],
]);

const neutralHueTones = [...hueTones, ...neutralHues.keys()];
export function setCSSVariablesFromM3Theme(hex: string) {
  const m3ThemeColorsJSON = themeFromSourceColor(argbFromHex(hex));
  for (const [variant, palette] of Object.entries(m3ThemeColorsJSON.palettes)) {
    const paletteKey = variant.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
    const tones = paletteKey === 'neutral' ? neutralHueTones : hueTones;
    for (const tone of tones) {
      const color = tone === 40 && paletteKey === 'primary' ? hex : hexFromArgb(palette.tone(tone));
      document.documentElement.style.setProperty(`--m3-${paletteKey}-color-${tone}`, color);
    }
  }
}

First you need to pnpm i @material/material-color-utilities which is used internally by the theme-color scheme.

My code is just a little change from what the scheme does node_modules/@angular/material/schematics/ng-generate/theme-color/index_bundled.js (getColorPalettesSCSS(colorPalettes) function)

Offcourse not an ideal thing to do, but now I can set a brand color to tone 40 and I have all tones as css variables from one hex value.

richardsengers avatar Jan 08 '25 10:01 richardsengers

As a status update, I looked into this a bit further and it isn’t so so straightforward unfortunately. As an example, take these two colors: #003846 (the darker blue) and #2ED5FF (the lighter blue).

Image Image Image

Tonal palettes are created from a color’s hue, but its tone is adjusted (which are the values seen in the tonal palette image above). Both of these colors share the same hue so they share the same tonal palette. Both of these themes would create the same value for the primary color role. The material components theming system picks the color from the palettes for any color role (ex. tone 40 for the primary color role).

The issue with changing the tonal palette itself is how the different colors relate to each other. For example if we tweak the tonal palette so that tone 40 matches either the lighter blue color #2ED5FF (which has a tone of ~79), the other tones need to be adjusted to account for color contrast. Although this could get weird because other palettes would also need to be adjusted, but those colors can also be customized.

It feels like because of the constraints of the material color system, it makes sense for applications who want exact colors to set things themselves to ensure there is adequate color contrast between their palettes. The color roles should be adjusted, not the tonal palettes themselves.

Although having access to the CSS vars of the tonal palettes as mentioned in #30070 is useful so you can do something like this in your application:

@include mat.theme-overrides((
  primary: var(—mat-sys-primary80),
));

I’ll leave this issue open since it seems useful to provide a more accurate theme to the provided colors. It would likely require more careful planning since it would cause us to move away from Material’s color system to make this work.

amysorto avatar Jan 16 '25 15:01 amysorto

" It would likely require more careful planning since it would cause us to move away from Material’s color system to make this work."

True, but Material 3 theming seems primarily tailored for mobile and Android apps. In contrast, many Angular Material components are used in large web-based applications (based on nothing, just what I think :-)), where achieving the same aesthetic appeal as on Android, particularly with different tonal palette colors, can be more challenging. For instance, consider a dashboard: many applications built with Angular Material include a header featuring a brand color.

Offcourse we can override everything... but like you said it seems useful to provide a more accurate theme to the provided colors for Angular Material

richardsengers avatar Jan 16 '25 16:01 richardsengers

Off topic: I have to say that I started using @angular/material:theme-color in a new project with the option to generate tokens as CSS instead of SASS, and I think it's infinitely better. For the first time, I feel like I have proper control over the components, I know what options are available, and I understand where specific properties come from. It's great that some edge-case properties are described in comments. It could be even more detailed, though, because, for example, I still don’t understand the purpose of "fixed" tokens or many from the Neutral group—where they might be needed and whether I can safely remove them. Nonetheless, I really want to appreciate the introduction of CSS-based tokens. Great job, it’s a so much better than mixins with scss! Thank you Angular Material Team😊

zygarios avatar Jan 18 '25 09:01 zygarios

@amysorto it seems like M3 has an option for Color Match

Image

https://material-foundation.github.io/material-theme-builder/

I think something like this should be added to the scheme, don't you think? I'm unable to determine what the option actually does behind the scenes.

richardsengers avatar Jan 28 '25 11:01 richardsengers

I've tried to use the generated palette from the material theme builder (the json export) but when I enable the color match option, the sass-loader of angular complains about invalid colors.

I think it would be good to have the option to color match when creating the custom theme with @angular/material:theme-color.

depyronick avatar Jan 31 '25 18:01 depyronick

I can highly recommend you to use CSS material generator pallete. Its do much better to use

zygarios avatar Jan 31 '25 19:01 zygarios

@amysorto it seems like M3 has an option for Color Match

Image https://material-foundation.github.io/material-theme-builder/

I think something like this should be added to the scheme, don't you think? I'm unable to determine what the option actually does behind the scenes.

@richardsengers What this option actually does is set dynamicSchemeVariant to DynamicSchemeVariant.content.

Tokens and palettes match the seed color. ColorScheme.primaryContainer is the seed color, adjusted to ensure contrast with surfaces. The tertiary palette is analogue of the seed color.

Glavo avatar Mar 16 '25 03:03 Glavo

I don't really quite why the the generated palette changes primary color from the one provided in the CLI.

It would be nice to have the additional option to force the color match, since the color I provide is what I want the palette --mat-sys-primary to be and not something darker.

peteqian avatar Apr 11 '25 12:04 peteqian

So I generate my colour palette (primary based on a colour, and that specific could will not be present in the generated list. That is not very handy. So then I add my specific color in one of the hue values, it is hue 80 for this color palette. But in my theme:

 @include mat.theme(
    (
      color: colors.$primary-palette,
      typography: (
        brand-family: Poppins,
        plain-family: 'Inter Tight'
      ),
      density: 0,
    )
  );

I set the primary palette and now my but on which is using --mat-sys-primary is selecting hue 40. So now I have to go override this in my button and other components?? This is not making it very easy. In m2 you could define which Hue to use as primary in your palette. Why did this functionality get removed?

I am really struggling with the colour system and how to override. The documentation is very lacking and even in Angular Material Components, it is not very clear how to adjust things properly.

I end up doing:

:root {
    // --mat-sys-on-primary: #66E0E0;
    --mat-sys-primary: #{get-palette-color($primary-palette, 80)};

Is that best practice? How about using:

@include mat.theme-overrides((
    // primary: #ebdcff,
    background: #000,
    on-surface: #fff,
    // sys-primary: red,

on-surface, suface, primary, etc... it is very confusing. I end up inspecting the element and see which var is used and try to override that :(

EDIT: I found out that :

@include mat.theme-overrides((
  primary: red
...

generates the --mat-sys-primary so I better use that override instead of using my :root override.

mattiLeBlanc avatar May 20 '25 11:05 mattiLeBlanc

All My Clients are raging about this theme change... Sorry but this was the worst update ever. I wish the color pallete to be as it was on M2

blogcraft avatar Jun 19 '25 14:06 blogcraft

This is simply frustrating. Did no one though about enterprise applications which has it's own design system and colors and would not allow to change colors?

I have this color as primary color. This is common color. Even metronic theme uses this.

Image

When I use this color as source then below is how my primary color looks like.

Image

This means that we can't use light blue as primary color at all. What kind of logic is this?

Should we just ask our produce team to redesign their colors to use use m3 specifics? Does this even make sense?

This means that we now have to stick to old version and can't upgrade angular.

This seems to be treated as minor issue. But it's not. This is really really big major issue. This is blocker for existing applications.

This will force large scale applications to move away from angular material and even angular.

Please take this issue seriously. CC: @amysorto

ankurlorecs avatar Jun 23 '25 10:06 ankurlorecs

@ankurlorecs I found you just ignore the colour palettes that are generated via the mat-theme-override:

  @include mat.theme-overrides(
    (
      background: #07060d,
      on-surface: #fff,
      inverse-on-surface: #000,
      // outline: #fff,
      primary: colors.get-palette-color(colors.$primary-palette, 50),
      on-primary: #fff,
      error: colors.get-palette-color(colors.$error-palette, 50),
      outline: #30578 ;

I even corrected some of the colors in the generated palette to match the colour I want. You can see in Angular material which css variable value is used and just override that one. I agree, the standard generate palettes are useless, they never match the colour our designers intent us to use.

mattiLeBlanc avatar Jun 23 '25 12:06 mattiLeBlanc

@mattiLeBlanc Yes. But If we just overrides the colors then it defeats the purpose of color pallets at all. I think this issue should not be labeled as minor issue(p4) and which would not be worked on for long time and becomes stale.

ankurlorecs avatar Jun 25 '25 12:06 ankurlorecs

@ankurlorecs yeah fair point; what is the usefulness of the generated palette if they do not cover or represent the colours of our company styleguide :/

mattiLeBlanc avatar Jun 25 '25 23:06 mattiLeBlanc

This is extremely annoying issues, since it is not obvious from the docs that using your custom defined colors, doesn't mean, that those will actually be used. Material builder actually supports this, but it is not evident how it is actually done...So you really have to dig deep, to understand the issue, and at the end come to this issues thread.

It seems there is some work going on though...:

https://github.com/andrewseguin/components/commit/c875178bb02924a8427a2e3a8329c7016d01f759

St1c avatar Jul 11 '25 12:07 St1c