angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.32k stars 6.72k forks source link

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

Open MikaStark opened 2 months ago

MikaStark commented 2 months ago

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
bramdonem commented 1 month ago

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 commented 1 month ago

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

MikaStark commented 1 month ago

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 commented 1 week ago

@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 commented 1 week ago

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 commented 1 day ago

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.