components icon indicating copy to clipboard operation
components copied to clipboard

feat(style): Runtime theme generation

Open tanishqmanuja opened this issue 1 year ago • 12 comments

Feature Description

The ability to generate theme based on a seed color at runtime instead of compile time.

Use Case

This is very common in native android apps and would also benefit ionic/capacitor user to make their app feel more native

tanishqmanuja avatar May 23 '24 04:05 tanishqmanuja

It's not properly documented yet, but there's a use-system-variables in the color and typography fields of M3 themes. If you set it to true, you'll be able to theme dynamically by changing a ~10 CSS variables at runtime. Here's how to pass it: https://github.com/angular/components/blob/main/src/dev-app/theme-m3.scss#L15

crisbeto avatar May 23 '24 04:05 crisbeto

Thanks, this is exactly what I needed. Can you also provide a link to find what CSS Vars are used ?

tanishqmanuja avatar May 23 '24 05:05 tanishqmanuja

Below are all the possible ones. You don't need to provide all of them, e.g. if you don't set use-system-variables for the typography, you can skip the whole typography section. We also have the mat.system-level-colors and mat.system-level-typography mixins that you can use to generate the values from a theme.

// Colors
--sys-background
--sys-error
--sys-error-container
--sys-inverse-on-surface
--sys-inverse-primary
--sys-inverse-surface
--sys-on-background
--sys-on-error
--sys-on-error-container
--sys-on-primary
--sys-on-primary-container
--sys-on-primary-fixed
--sys-on-primary-fixed-variant
--sys-on-secondary
--sys-on-secondary-container
--sys-on-secondary-fixed
--sys-on-secondary-fixed-variant
--sys-on-surface
--sys-on-surface-variant
--sys-on-tertiary
--sys-on-tertiary-container
--sys-on-tertiary-fixed
--sys-on-tertiary-fixed-variant
--sys-outline
--sys-outline-variant
--sys-primary
--sys-primary-container
--sys-primary-fixed
--sys-primary-fixed-dim
--sys-scrim
--sys-secondary
--sys-secondary-container
--sys-secondary-fixed
--sys-secondary-fixed-dim
--sys-shadow
--sys-surface
--sys-surface-bright
--sys-surface-container
--sys-surface-container-high
--sys-surface-container-highest
--sys-surface-container-low
--sys-surface-container-lowest
--sys-surface-dim
--sys-surface-tint
--sys-surface-variant
--sys-tertiary
--sys-tertiary-container
--sys-tertiary-fixed
--sys-tertiary-fixed-dim

// Typography
--sys-body-large
--sys-body-large-font
--sys-body-large-line-height
--sys-body-large-size
--sys-body-large-tracking
--sys-body-large-weight
--sys-body-medium
--sys-body-medium-font
--sys-body-medium-line-height
--sys-body-medium-size
--sys-body-medium-tracking
--sys-body-medium-weight
--sys-body-small
--sys-body-small-font
--sys-body-small-line-height
--sys-body-small-size
--sys-body-small-tracking
--sys-body-small-weight
--sys-display-large
--sys-display-large-font
--sys-display-large-line-height
--sys-display-large-size
--sys-display-large-tracking
--sys-display-large-weight
--sys-display-medium
--sys-display-medium-font
--sys-display-medium-line-height
--sys-display-medium-size
--sys-display-medium-tracking
--sys-display-medium-weight
--sys-display-small
--sys-display-small-font
--sys-display-small-line-height
--sys-display-small-size
--sys-display-small-tracking
--sys-display-small-weight
--sys-headline-large
--sys-headline-large-font
--sys-headline-large-line-height
--sys-headline-large-size
--sys-headline-large-tracking
--sys-headline-large-weight
--sys-headline-medium
--sys-headline-medium-font
--sys-headline-medium-line-height
--sys-headline-medium-size
--sys-headline-medium-tracking
--sys-headline-medium-weight
--sys-headline-small
--sys-headline-small-font
--sys-headline-small-line-height
--sys-headline-small-size
--sys-headline-small-tracking
--sys-headline-small-weight
--sys-label-large
--sys-label-large-font
--sys-label-large-line-height
--sys-label-large-size
--sys-label-large-tracking
--sys-label-large-weight
--sys-label-large-weight-prominent
--sys-label-medium
--sys-label-medium-font
--sys-label-medium-line-height
--sys-label-medium-size
--sys-label-medium-tracking
--sys-label-medium-weight
--sys-label-medium-weight-prominent
--sys-label-small
--sys-label-small-font
--sys-label-small-line-height
--sys-label-small-size
--sys-label-small-tracking
--sys-label-small-weight
--sys-title-large
--sys-title-large-font
--sys-title-large-line-height
--sys-title-large-size
--sys-title-large-tracking
--sys-title-large-weight
--sys-title-medium
--sys-title-medium-font
--sys-title-medium-line-height
--sys-title-medium-size
--sys-title-medium-tracking
--sys-title-medium-weight
--sys-title-small
--sys-title-small-font
--sys-title-small-line-height
--sys-title-small-size
--sys-title-small-tracking
--sys-title-small-weight

crisbeto avatar May 23 '24 06:05 crisbeto

That's awesome, they are the same as material-web style tokens with just different prefix --md-sys <-> --sys. With material-color-utilities npm package, it should be super simple to provide these.

tanishqmanuja avatar May 23 '24 07:05 tanishqmanuja

AFAIK material-color-utilities don't inject surface tokens as of now, here's a snippet if anyone needs it.

import { hexFromArgb, Theme } from "@material/material-color-utilities";

export function applySurfaceStyles(
  theme: Theme,
  { dark }: { dark: boolean },
): void {
  if (dark) {
    const elevationProps = {
      "--md-sys-color-surface-dim": theme.palettes.neutral.tone(6),
      "--md-sys-color-surface-bright": theme.palettes.neutral.tone(24),
      "--md-sys-color-surface-container-lowest": theme.palettes.neutral.tone(4),
      "--md-sys-color-surface-container-low": theme.palettes.neutral.tone(10),
      "--md-sys-color-surface-container": theme.palettes.neutral.tone(12),
      "--md-sys-color-surface-container-high": theme.palettes.neutral.tone(17),
      "--md-sys-color-surface-container-highest":
        theme.palettes.neutral.tone(22),
    };

    for (const [property, argbColor] of Object.entries(elevationProps)) {
      document.body.style.setProperty(property, hexFromArgb(argbColor));
    }
  } else {
    const elevationProps = {
      "--md-sys-color-surface-dim": theme.palettes.neutral.tone(87),
      "--md-sys-color-surface-bright": theme.palettes.neutral.tone(98),
      "--md-sys-color-surface-container-lowest":
        theme.palettes.neutral.tone(100),
      "--md-sys-color-surface-container-low": theme.palettes.neutral.tone(96),
      "--md-sys-color-surface-container": theme.palettes.neutral.tone(94),
      "--md-sys-color-surface-container-high": theme.palettes.neutral.tone(92),
      "--md-sys-color-surface-container-highest":
        theme.palettes.neutral.tone(90),
    };

    for (const [property, argbColor] of Object.entries(elevationProps)) {
      document.body.style.setProperty(property, hexFromArgb(argbColor));
    }
  }
}

Change the prefix as per requirement 🤞🏻

tanishqmanuja avatar May 23 '24 07:05 tanishqmanuja

There is a new option to configure the prefix of system variables.

Read more about this in my article, which includes a live demo: https://konstantin-denerz.com/angular-material-3-theming-design-tokens-and-system-variables/

konstantindenerz avatar Jul 09 '24 20:07 konstantindenerz

Offtopic - Your site looks really amazing, I have mostly seen bottom navs (on mobile view) with icons but your text based bottom nav is 🔥

tanishqmanuja avatar Jul 10 '24 04:07 tanishqmanuja

First attempt after reading @konstantindenerz article https://github.com/tanishqmanuja/demo.ng-material-dynamic-theme

Is this inline with the best practices suggested by angular-material ?

PS: I am still confused about how to inject typography tokens

tanishqmanuja avatar Aug 10 '24 08:08 tanishqmanuja

For one of my article, I used applyTheme from @material/material-color-utilities, and it generated all the needed colors. You can read the article with demo at https://angular-material.dev/articles/angular-material-theming-css-vars

shhdharmen avatar Sep 03 '24 06:09 shhdharmen

Is there a way to do this at component level easily.

image image

The double inheritance problem, Why this wont work for individual components? - lit playground

Also this is a very common use-case, suppose i want to display multiple color schemes option at once to a user.

PRACTICAL NEED: Suppose I make a theme service that applies the dynamically generated css vars like --md-sys-* etc to the host component where the service is provided, the service depends on a minimal injection token SOURCE_COLOR for generating the color scheme. Now the problem arises because even though every thing should work in theory, as it works with lit based material components (which are now on maintenance mode, and the docs reads "use angular material for angular framework" ), but due to inherited css vars used in angular-material components, there is no way to make such a thing work with angular-material. Any thoughts ?

tanishqmanuja avatar Sep 13 '24 10:09 tanishqmanuja

At component level angular material uses lots of internal variables. But, with Angular Material 18, team have introduced overrides API, you can use it like below, of course, for runtime changes you will still need to figure out all of CSS (--mat-* and --mdc-*) the variables.

button {
    @include mat.button-overrides((
        ripple-color: red
    ))
}

shhdharmen avatar Sep 13 '24 16:09 shhdharmen

figuring out all those without docs doesnt sound too good either. So that's a no for sure :(

tanishqmanuja avatar Sep 13 '24 18:09 tanishqmanuja

I used the generator on the latest version and I get his neutra color:

Image

Which make shadows look silly

Image

This is not what buttons look like on the angular component page

Image

When inspecting, I can see it is a shade of white with some alpha applied

Image

But that's not what is generated for me:

Image

worthy7 avatar Jan 21 '25 12:01 worthy7