Develop an original angular material theme utilizing CSS variables

I have been working on a project that requires runtime theming. To achieve this, I developed a theme system that combines SCSS variables with CSS Variables. Here's an example of how it functions.

:root {
  --primary-color: 196;
}


// Primary
$primary-100: hsl(var(--primary-color), 90%, 98%);
$primary-400: hsl(var(--primary-color), 90%, 65%);
$primary-main: hsl(var(--primary-color), 90%, 50%);
$primary-700: hsl(var(--primary-color), 90%, 30%);
$primary-900: hsl(var(--primary-color), 90%, 10%);

While this approach works seamlessly with my custom components, I am encountering difficulties when trying to integrate it with the Material design theme system.

My initial plan was to follow the guidelines outlined in the Angular material documentation and utilize my SCSS variables instead of static colors. Below is an excerpt from my theme.scss file depicting this concept.

@import '~@angular/material/theming';
@import 'var.scss';

@include mat-core();

$shop-primary: (
  50: $primary-100,
  100: $primary-100,
  200: $primary-200,
  300: $primary-300,
  400: $primary-400,
 // ..... all other colors
  contrast: (
    50: $black-87-opacity,
    100: $black-87-opacity,
    200: $black-87-opacity,
     // ..... all other colors
  )
);


$shop-app-primary: mat-palette($shop-primary);
$shop-app-accent:  mat-palette($shop-primary);
$shop-app-warn: mat-palette($shop-primary);

$shop-app-theme: mat-light-theme($shop-app-primary, $shop-app-accent, $shop-app-warn);

@include angular-material-theme($shop-app-theme);

However, I encountered the following error:

 Argument `$color` of `rgba($color, $alpha)` must be a color

This error seems to be caused by the fact that the Angular Material mixin expects a valid color, rather than an hsl() value.

Therefore, my query is how can I successfully create a customized material theme utilizing runtime CSS variables?

Answer №1

I decided to simplify things by creating a handy little library.

Here's how you can utilize it:

  1. First, install it using the following command:

    npm i angular-material-css-vars -S
    
  2. Next, make sure to remove any existing

    @import '~@angular/material/theming';
    from your main stylesheet file.

  3. Instead, add the following lines to your main stylesheet:

    @import '~angular-material-css-vars/main';
    @include initMaterialCssVars();
    
  4. To change the main theme color, use this code snippet:

    import {MaterialCssVarsService} from 'angular-material-css-vars';
    
    export class SomeComponentOrService {
      constructor(public materialCssVarsService: MaterialCssVarsService) {
        const hex = '#3f51b5';
        this.materialCssVarsService.changePrimaryColor(hex);
      }
    }
    

Answer №2

If you decide to upgrade to @angular/material 7.3.4, CSS Variables should function properly for the most part. The only adjustments needed will be for ripples and any other elements utilizing opacity. In my project, I utilize rgba(), but hsla() should also suffice.

Here is what you need to include:

@function mat-color($palette, $hue: default, $opacity: null) {
    @if type-of($hue) == number and $hue >= 0 and $hue <= 1 {
        @return mat-color($palette, default, $hue);
    }

    $color: map-get($palette, $hue);

    @if (type-of($color) != color) {
        @if ($opacity == null){
            @return $color;
        }

        // Modification made to the original function:
        // If the $color does not resolve to a color, we assume it's a CSS variable in the form of rgba(var(--rgba-css-var),a) and replace the 'a' value.
        @return #{str-slice($color, 0, str-index($color, ',')) + $opacity + ')'};
    }

    @return rgba($color, if($opacity == null, opacity($color), $opacity));
}

Place this code directly after:

@import '~@angular/material/theming';

and make sure to define your colors like this:

--primary-color-50-parts: 0,158,224;
// ... all other colors

$color-primary: (
    50: rgba(var(--primary-color-50-parts), 1),
    // ... all other colors
);

If you're defining your colors in the map as shown below:

50: hsla(var(--primary-color), 90%, 98%, 1);

you'll need to change str-index($color, ',') in the sass function to locate the last ',' in the string. Unfortunately, my knowledge of Sass is quite basic, and I'm unsure how to achieve that :/

Answer №3

I have developed a small library called material-theme-creator

This tool allows you to customize the theme of your angular-application or create new themes easily

NPM: https://www.npmjs.com/package/material-theme-creator

DOCS:

npm i material-theme-creator

@import "~material-theme-creator/ngx-mtc";
@import '~@angular/material/theming';

@include mat-core();
@include ngx-mtc-init();

$primary-map: ngx-mtc-create-theme-map('primary');
$accent-map: ngx-mtc-create-theme-map('accent');
$warn-map: ngx-mtc-create-theme-map('warn');

:root {
  --is-dark-theme: 1; // Set to 1 for dark theme, 0 for light theme
  @include ngx-mtc-theme-base(); // Sets base colors

  // Define theme colors
  @include ngx-mtc-create-variables-from-color('primary', #009688, 38%);
  @include ngx-mtc-create-variables-from-color('accent', #2196f3, 57%);
  @include ngx-mtc-create-variables-from-color('warn', #f44336, 62%);
}

// Create Angular Material Theme
@include angular-material-theme(
  ngx-mtc-custom-theme(
    mat-palette($primary-map),
    mat-palette($accent-map),
    mat-palette($warn-map)
  )
);
 

The code snippet for a second theme:

.second-theme {
  --is-dark-theme: 0;
  @include ngx-mtc-update-theme('primary', #142148, 45%);
  @include ngx-mtc-update-theme('accent', #658e14, 50%);
  @include ngx-mtc-update-theme('warn', #750101, 50%);
}

You can use this library with Angular Material, SCSS, or pure CSS

Answer №4

It's quite simple to make this happen right from the start.

To get started, establish a palette and variables for Primary, Accent, and Warn based on the angular palette found in your themes.scss file. Check out this link for guidance.

@import '@angular/material/theming';
@include mat.core();

$dark-primary-text: rgba(black, 0.87);
$dark-secondary-text: rgba(black, 0.54);
$dark-disabled-text: rgba(black, 0.38);
$dark-dividers: rgba(black, 0.12);
$dark-focused: rgba(black, 0.12);
$light-primary-text: white;
$light-secondary-text: rgba(white, 0.7);
$light-disabled-text: rgba(white, 0.5);
$light-dividers: rgba(white, 0.12);
$light-focused: rgba(white, 0.12);

$primary-var-palette: (
  50: var(--primary-50, #fafafa),
  100: var(--primary-100, #f5f5f5),
  200: var(--primary-200, #eeeeee),
  300: var(--primary-300, #e0e0e0),
  400: var(--primary-400, #bdbdbd),
  500: var(--primary-500, #9e9e9e),
  600: var(--primary-600, #757575),
  700: var(--primary-700, #616161),
  800: var(--primary-800, #424242),
  900: var(--primary-900, #212121),
  A100: var(--primary-A100, #ffffff),
  A200: var(--primary-A200, #eeeeee),
  A400: var(--primary-A400, #bdbdbd),
  A700: var(--primary-A700, #616161),
  contrast: (
    50: $dark-primary-text,
    100: $dark-primary-text,
    200: $dark-primary-text,
    300: $dark-primary-text,
    400: $dark-primary-text,
    500: $light-primary-text,
    600: $light-primary-text,
    700: $light-primary-text,
    800: $light-primary-text,
    900: $light-primary-text,
    A100: $dark-primary-text,
    A200: $light-primary-text,
    A400: $light-primary-text,
    A700: $light-primary-text,
  )
);

Next, add the palettes to the theme.

$var-primary: mat.define-palette($primary-var-palette);
$var-accent:  mat.define-palette($accent-var-palette, 400, 900, A100);
$var-warn:    mat.define-palette($warn-var-palette);
$var-theme: mat.define-light-theme($var-primary, $var-accent, $var-warn);
@include mat.all-component-themes($var-theme);

You cannot directly set or change the CSS variable in HTML, so modify the theme dynamically.

document.documentElement.style.setProperty('--primary-50', myPrimary50Value);
...

Answer №5

Did you know that CSS variables are evaluated at runtime, not compile time? This means that SASS doesn't have the capability to handle them directly. However, you can still refactor your CSS variables by utilizing the ${} interpolation in SCSS and maintain the same functionality. Check out this resource for more information: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#interpolation_

$primary-color: 196;

:root {
  --primary-color: #{$primary-color};
}

$primary-100: hsl($primary-color, 90%, 98%);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ways to optimize image focus on a webpage

Upon loading the page, I desire for my image to be automatically focused. Unfortunately, setting the tabindex parameter has not produced the desired outcome. This is likely due to the fact that there are multiple angular2 header components with defined t ...

rectangle/positionOffset/position().top/any type of element returning a position value of 0 within the container

While the height/position of the container is accurately displayed, attempting to retrieve the top position (or any position) of containing elements yields a return value of 0. Additionally, using .getBoundingClientRect() results in all values (top, left, ...

Tips for building a dynamic view using Angular

I am working on a project where I need to dynamically generate multiple horizontal lines using data from a JSON file, similar to the example in the image below. https://i.sstatic.net/MthcU.png Here is my attempt: https://i.sstatic.net/EEy4k.png Component. ...

Completely shrink the middle row first when resizing the browser before shrinking the other rows

Utilizing a simple three row structure: <div class="EditorHeaderWrapper"> <h1>Title</h1> </div> <div class="EditorMainRowWrapper"> // Content of the main row goes here </div> <div class="EditorFooterWrapper"> ...

Encountering a problem with the chipGrid feature in Angular Material version

I am currently facing an issue with my angular material chip component. The versions I am using are Angular 16 and Material 16. Here are all my dependencies: "@angular/animations": "^16.0.4", "@angular/cdk": "^16.0.4&quo ...

Unable to modify the background image of the button

Currently, I am working on this element I am trying to insert a background image into the "Click" button, but I am not seeing any results. Here is the HTML and CSS I am using: .cn-button { position: absolute; top: 100%; left: 50%; z-ind ...

Passing a variable from the second child component to its parent using Angular

Need help with passing variables between two child components Parent Component: deposit.component.html <div> <app-new-or-update-deposit [(isOpenedModal)]="isOpenedModal"></app-new-or-update-deposit> </div> Deposit Component ...

The Popover style from React-Bootstrap seems to be missing when applied to my component

Attempting to incorporate a Popover with overlay trigger from React - Bootstrap has proven to be quite the challenge. If you check this image, you'll see what it's supposed to look like. This is the code I used: var {Button, Overlay, OverlayTr ...

Embedding an image by inserting the URL

I have been attempting to implement functionality in Typescript where an image preview appears after a user enters a URL, but I have only been successful in achieving this in JavaScript. My goal is to display a preview of the image and enable the user to u ...

The functionality of scrolling ceases to work once the bootstrap modal is closed

I am utilizing Bootstrap v3.1.1 to showcase a modal popup on one of my web pages. The following code shows how I have implemented it. <!--Start show add student popup here --> <div class="modal fade" id="add-student" tabindex="-1" role="dialog" a ...

Spacing applied to content within a fresh Vue application

Having been assigned the task of developing a Vue page for my team, I am facing an issue with a persistent white border surrounding the entire page. <script setup lang="ts"> </script> <template> <body> <div>&l ...

There appears to be an issue with the dynamic functionality of RouterLink in Angular 6

user-dashboard.html <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link" routerLink='/dashboard'>User Dashboard</a> </li> <li class="nav-item" *ngFor="let cat of categories; let i = in ...

Ways to remove content that exceeds the boundaries of a div element

I am facing a challenge with a parent div that has a specified size. Inside this parent div, there are other child divs, and if any of those child divs start displaying their content outside of the parent div, I want to completely remove that div. I do not ...

Is there a way to access a comprehensive list of all the font names stored on my personal computer?

I'm currently working on a project that requires using local fonts only. Since I am using a Mac, I have located the fonts in /Library/Fonts. However, I have encountered an issue when trying to use a font named 华文黑体 directly in my CSS property: ...

modifying the child divs contained in the main div

Hey there! I'm currently using Bootstrap to design an application and I've got the entire form inside a container. However, as I add more child elements within the container div, I would like the parent div to expand automatically to show all the ...

Determine if there is at least one element that is true in the array

I am currently working on developing a menu that can detect the active page based on the site's structure, which is purely PHP/HTML without any CMS. While I have successfully implemented navigation for primary links, I am facing issues with the dropd ...

Align the positioning of the dropdown menu and the input field

I'm having trouble with css and ng2-completer. I am attempting to line up the dropdown section with the input field. Unfortunately, there is no demo example provided on the page showing how to target elements using css. I've tried selecting the ...

Behold the magic of a three-column layout with sticky scroll behavior and an absolute

I am struggling with a layout that involves 3 columns, each with its own independent scroll. The first column contains an absolute element that should overflow on the x-axis, but I am having trouble getting the overflow-x:visible property to work when comb ...

"The ion-label in Ionic2 is cutting off some of the text and not displaying it

I'm currently facing an issue with ion-label inside ion-item. The description is not properly displaying and instead, it shows dot-dot.. ..I need the entire text to be visible. Is there any solution available? <ion-card *ngFor="let product of prod ...

Troubleshooting the Div Ordering Problem in Bootstrap 4

Currently, I am using Bootstrap 4 and facing an issue with the layout order. Despite searching for a solution extensively, I have not been able to get it right or find one in the archives. Here is what I need... For desktop view, the layout should appear ...