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

feat(theming): Replace SASS-Based theming with global CSS variables #29363

Open
MikaStark opened this issue Jul 2, 2024 · 17 comments · May be fixed by #30004
Open

feat(theming): Replace SASS-Based theming with global CSS variables #29363

MikaStark opened this issue Jul 2, 2024 · 17 comments · May be fixed by #30004
Labels
area: theming feature This issue represents a new feature or feature request rather than a bug or bug fix needs: discussion Further discussion with the team is needed before proceeding P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Comments

@MikaStark
Copy link

Feature Description

Motivation

This feature request aims to simplify and consolidate CSS code instead of splitting it into multiple mixins. The current recommended way of theming components requires writing a dedicated file with at least three mixins: theme, color, and typography (and density?). While this approach follows a good philosophy of separating concerns, it has some significant drawbacks:

1. Exponential Growth and Difficult Maintenance

In large projects with hundreds of components, having a separate theme for each component can become boilerplate-heavy and lead to tedious maintenance. For example, the material.angular.io app theme imports and uses all themes scattered across the app. Scaling this approach to a much larger project would be painful and error-prone. The component-attached stylesheet is no longer the single source of truth.

2. Excessive Boilerplate

Creating a new component requires writing a dedicated theme that we have to import and use in styles.scss. This adds unnecessary complexity to the development process.

3. Lack of Component View Encapsulation

One of Angular's cool features is view encapsulation, which prevents style collisions. However, since themes need to be applied in the main styles.scss file, encapsulation cannot be used. This forces us to rely on verbose CSS class names (e.g., app-my-component-header-title), whereas a simpler header-title would suffice when wrapped in an encapsulated stylesheet.

4. Dependence on SASS

To customize Angular Material, you have to use SASS, but some developers might prefer not to rely on it for various reasons. Additionally, ng new offers different stylesheet formats, which means forcing SASS can be limiting.

Proposed Alternative

Inspired by the new Material 3 use-system-variables capability, I propose using a set of CSS variables (e.g., --sys-primary, --sys-body-large, etc.) available globally for Angular Material and custom components, instead of relying on SASS mixins. Of course, we could consider having a schematics to help developer to generate those variables just like ng generate @angular/material:m3-theme does.

@crisbeto listed those variables in this comment

Also, using css variables only instead of SASS theme simplify overrides and remove the need of Angular components mixins (like mat.button-theme($my-theme)).

However, I can understand we still need to import core styles. This could be easily addressed by registering the right css file in angular.json file in build styles option list.

"styles": [
  "src/styles.css",
  "path/to/angular-material-component-core.css"
],

Problems to Consider

1. Conditional Logic Based on Theme Type (Dark or Light)

We have to find a css alernative to get-theme-type($theme).

color: if(mat.get-theme-type($theme) == dark, white, black);

Native CSS does not provide an if conditional operator, but the CSS container property could be a viable alternative (cf. https://stackoverflow.com/a/76327443).

2. Reading Palettes' Hues

Without SASS, palettes will not be available. However, this issue could be mitigated by adding more CSS variables (e.g., --sys-primary-80, --sys-secondary-20).

To sumarize

This proposal aims to streamline the theming process in Angular Material by reducing boilerplate, enhancing maintainability, and leveraging modern CSS capabilities. It aligns with the broader trend of using CSS variables for theming and can simplify the theming experience for developers.

Thank you for considering this feature request. I believe it can significantly improve the development workflow and theming consistency in Angular Material projects.

Use Case

We could replace the following code :

// styles.scss

@use '@angular/material' as mat;

@include mat.core();

$my-theme: mat.define-theme((
 color: (
    theme-type: light,
    primary: mat.$violet-palette,
  ),
));

html {
  @include mat.all-component-themes($my-theme);
}

// carousel.scss

.my-carousel {
  display: flex;
}

.my-carousel-button {
  border-radius: 50%;
}

// _carousel-theme.scss

@use 'sass:map';
@use '@angular/material' as mat;

@mixin color($theme) {
  .my-carousel-button {
    // Read the 50 hue from the primary color palette.
    color: mat.get-theme-color($theme, primary, 50);
  }
}

@mixin typography($theme) {
  .my-carousel {
    // Get the large headline font from the theme.
    font: mat.get-theme-typography($theme, headline-large, font);
  }
}

@mixin theme($theme) {
  @if mat.theme-has($theme, color) {
    @include color($theme);
  }

  @if mat.theme-has($theme, typography) {
    @include typography($theme);
  }
}

by this lighter code in pure css :

// styles.css

html {
  --sys-primary: #xxxxxx;
  --sys-secondary: #xxxxxx;
  --sys-tertiary: #xxxxxx;
  --sys-error: #xxxxxx;
  // and so on...
}

// carousel.css

.my-carousel {
  display: flex;
  font: var(--sys-headline-large);
}

.my-carousel-button {
  border-radius: 50%;
  color: var(--sys-primary-50);
}

// no more need for _carousel-theme.scss
@MikaStark MikaStark added feature This issue represents a new feature or feature request rather than a bug or bug fix needs triage This issue needs to be triaged by the team labels Jul 2, 2024
@bramdonem
Copy link

I'm still trying to understand why this wasn't implemented in the first place like this. There must have been a reason for it?

Anyone could help me figure out why?

@crisbeto
Copy link
Member

crisbeto commented Aug 6, 2024

For a long time we supported IE11 which prevented us from using CSS variables for theming.

@mmalerba mmalerba added P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent needs: discussion Further discussion with the team is needed before proceeding area: theming and removed needs triage This issue needs to be triaged by the team labels Aug 6, 2024
@MikaStark
Copy link
Author

It's worth noting that using CSS variables for the Material theme allows developers to leverage the Material Theme Builder. This approach reduces the dependency on schematics for generating theme tokens, streamlining the theming process.

@zygarios
Copy link

zygarios commented Sep 3, 2024

@MikaStark everything what you wrote in your post is exactly what I keep thinking about when I have to add material to the project. I believe that the current approach is overly complicated and everything should be based on CSS variables. While I like SASS, in the case of material configuration, I hope they move away from dependency on SASS.

@MikaStark
Copy link
Author

To be honest, this is my current top 1 Angular Material pain point. I really hope this issue will get more and more thumb up and convince Angular Team that simplifying theming is an absolute need.

@probert94
Copy link

There is already a similar issue, where I also added my current solution.
Basically, I only use a single, global theming file, which reads all variables from the material-theme and creates the css custom properties for them.
The custom logic regarding theme types is in my opinion not needed. You can define your dark-theme variables under @media (prefers-color-scheme: dark) or to allow manualy switching under body.dark and css takes care of all the rest.

@MikaStark
Copy link
Author

I recently discovered that native CSS now supports nesting, and it’s well-supported across modern browsers (check it out here: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting

Since I primarily use SASS for its nesting capabilities, I'm seriously considering migrating to native CSS to achieve faster build times and a smaller CSS bundle size. Material SASS theming has been the only thing holding me back from making the switch.

@JPCodeCraft
Copy link

JPCodeCraft commented Oct 17, 2024

Using use-system-variables: true achieves the same result, right? Or am I missing something?

I never really understood the whole quadrillion mixins thing to be honest, so what I did was:

  1. Add a base theme
$my-theme: mat.define-theme(
  (
    color: (
      use-system-variables: true,
      system-variables-prefix: md-sys-color,
    ),
    typography: (
      use-system-variables: true,
      system-variables-prefix: md-sys-typescale,
    ),
    density: (
      scale: -1,
    ),
  )
);

:root {
  @include mat.all-component-themes($my-theme);
  @include mat.system-level-colors($my-theme);
  @include mat.system-level-typography($my-theme);
}
  1. Create themes I want using Material Theme Builder and copy the exported css

  2. Have a .scss file for each actual theme I want to use in my app
    Image

.afm-dark {
  --md-sys-color-primary: rgb(255 181 158);
  --md-sys-color-surface-tint: rgb(255 181 158);
  --md-sys-color-on-primary: rgb(85 32 13);
  --md-sys-color-primary-container: rgb(114 53 33);
  --md-sys-color-on-primary-container: rgb(255 219 208);
  --md-sys-color-secondary: rgb(231 189 177);
  --md-sys-color-on-secondary: rgb(68 42 34);
  --md-sys-color-secondary-container: rgb(93 64 55);
  --md-sys-color-on-secondary-container: rgb(255 219 208);
  --md-sys-color-tertiary: rgb(250 186 114);
  --md-sys-color-on-tertiary: rgb(72 41 0);
  --md-sys-color-tertiary-container: rgb(103 61 0);
  --md-sys-color-on-tertiary-container: rgb(255 221 186);
  --md-sys-color-error: rgb(255 180 171);
  --md-sys-color-on-error: rgb(105 0 5);
  --md-sys-color-error-container: rgb(147 0 10);
  --md-sys-color-on-error-container: rgb(255 218 214);
  --md-sys-color-background: rgb(26 17 15);
  --md-sys-color-on-background: rgb(241 223 218);

continues....
  1. Import those files in my styles.scss
@import "./themes/afm.scss";
@import "./themes/caerleon.scss";
@import "./themes/lymhurst.scss";
@import "./themes/mono.scss";
  1. Change the class in the root to match the theme the user picked

Is this a viable option?

@tp1906
Copy link

tp1906 commented Oct 18, 2024

Using the option, use-system-variables: true, creates system variables for a small subset of colors (primary, on-primary, secondary, on-secondary, secondary-container, etc.). Is there a way to access the tonal colors via system variables, for instance, --sys-color-primary-50, or do I have to use the color-mix function to get a different shade of primary, secondary, tertiary?

@MikaStark
Copy link
Author

@JPCodeCraft Yes, I think your solution is pretty nice and reflect what I tought when I wrote this issue. But still you need SASS and to rely on Material Components Themes (@include mat.all-component-themes($my-theme)).

@tp1906 You can easily manage the palette shades missing by yourself with few lines of codes. But yes, it would be nice to have a built-in solution...

--sys-color-primary-50: #{mat.get-theme-color($theme, primary, 50)};

@amysorto
Copy link
Contributor

I am closing this issue due to some of the updates coming to the theming system.

Starting in v19, there is a new theme() mixin that will greatly simplify theming files. By default that mixin creates global variables so there's no need to have to write use-system-variables: true. The theming guide will be updated with more details and instructions. There is even support to set colors using light-dark() for automatic light/dark theme toggling.

But as a preview, here is some sample code for a possible theme file on v19.

@use '@angular/material' as mat;

html {
  @include mat.theme((
    color: (
      theme-type: light,
      primary: mat.$red-palette,
    ),
    typography: Roboto,
    density: 0,
  ));
}

The theme() function creates system variables which are CSS variables that all the components use. You can reference those system variables in your custom styles when you need those values.

.my-button {
  color: var(--mat-sys-primary);
}

@teohhanhui
Copy link

teohhanhui commented Oct 31, 2024

@amysorto That still requires SASS. This issue is about switching away from SASS, isn't it?

Perhaps a new issue should be opened if the change only addresses part of the issue.

Or perhaps there will be good documentation for theming without using SASS at all?

@amysorto
Copy link
Contributor

@teohhanhui It does require SASS in the initial theme file but these changes do allow for usage of CSS variables throughout one's app. There are benefits to doing so this way such as error validation (misspelling a variable wouldn't throw an error for example).

@andrewseguin should there be documentation for using themes without SASS (assuming you can just set these system variables in a CSS file and everything works)?

@MikaStark
Copy link
Author

@teohhanhui raises a valid point. The primary focus of this issue is to transition away from SASS and fully embrace CSS. I believe this issue should remain open for the following reasons:

  1. The upcoming theming API is not yet implemented, so we lack evidence that it will effectively address the current issues.
  2. SASS continues to be the primary method for implementing themes, which may not align with our goal of simplifying the theming process.
  3. Utilizing CSS variables offers a straightforward and comprehensive approach for managing overrides, such as primary color, as needed.
  4. There is a growing trend towards native CSS as the preferred method for styling, which aligns with modern best practices.

@amysorto
Copy link
Contributor

Reopening this issue, thanks for all of your feedback.

I have a PR to add to the custom theme schematic the ability to generate CSS output (#30004). This will directly defines all the different CSS system level variables. This change will likely be in the first patch of v19. The recommendation is still to use sass, but hopefully this provides a path for developers who want to use only CSS.

@MikaStark
Copy link
Author

Thank you @amysorto, I’m so glad this problematic is taken so seriously. Thank you again for your comprehension and your hard work.

Can wait to see native CSS becoming the recommended way but still the new theme API seems promising anyway 😉

@MikaStark
Copy link
Author

I migrated one of my project to the new theme mixin and it works really great an is really simple to use. Great job !

The only drawback I see for now is the lack of palette shades through CSS variables.

I managed to generate it myself like so, but I would prefer mat.theme mixin do it instead :

@use "theme-colors" as colors;

@each $palette, $shades in colors.$palettes {
  @each $shade, $color in $shades {
    --mat-sys-#{$palette}-#{$shade}: #{$color};
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: theming feature This issue represents a new feature or feature request rather than a bug or bug fix needs: discussion Further discussion with the team is needed before proceeding P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants