Cory Rylan

My name is , Google Developer Expert, Speaker, Software Developer. Building Design Systems and Web Components.

Follow @coryrylan
Angular

Theming Angular apps with CSS Custom Properties

Cory Rylan

-

Updated

This article has been updated to the latest version Angular 17 and tested with Angular 16. The content is likely still applicable for all Angular 2 + versions.

Theming Angular apps has never been easier using CSS Custom Properties. CSS Custom Properties allow us to define our own custom variables for CSS values that we can share between components and style rules.

A common use case for using Sass or Less variables is to make it easy to change the value in one place and have it propagate to all CSS rules. CSS Custom Properties work the same way but also allow us to change the value at runtime. Updating CSS Custom Properties at runtime open up the opportunity to easily create themes for our Angular application.

In our use case, we will have two themes, first a default light theme and second and optional default theme.

Custom Light Angular Theme with CSS Custom Properties Custom Dark Angular Theme with CSS

In our Angular app, we have some default global styles as well as component level styles. This app will have two components, a header, and card component. Both of these components will use CSS variables (custom properties) to update their values dynamically. First, let's take a look at the app component template.

<app-header></app-header>

<main>
  <section>
    <app-card>
      Card 1
    </app-card>
    <app-card>
      Card 2
    </app-card>
    <app-card>
      Card 3
    </app-card>
  </section>

  <label for="darkTheme">Dark Theme</label>
  <input id="darkTheme" type="checkbox" [formControl]="darkTheme" />
</main>

In our template, we have our header and card components. The template also has a single checkbox we will use to toggle our theme. Next, let's take a look at the TypeScript of our app component.

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

import { ThemeService } from './theme.service';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  darkTheme = new FormControl(false);

  constructor(private themeService: ThemeService) {
    this.darkTheme.valueChanges.subscribe(value => {
      if (value) {
        this.themeService.toggleDark();
      } else {
        this.themeService.toggleLight();
      }
    });
  }
}

In our component, we have a single FormControl. We subscribe to the value changes when the user selects the checkbox. We have a theme service that will be responsible for toggling between the different CSS themes. Before we jump into our theme service lets take a look at our global.css file.

CSS Custom Properties

In our global.css file we define the CSS Custom Properties we want to share application wide.

:root {
  --primary-color: #fff; // white
  --background-color: #e5e5e5; // light grey
  --text-color: #2d2d2d; // dark grey
}

In our global.css file we have our CSS custom properties defined in a :root selector. The root selector at the global level will allow us to define the custom properties on the entire document globally. Global variables in CSS will enable us to define theme variables that multiple components can use. To define a CSS custom property, we must prefix the property key with two dashes like the following --primary-color: #fff;. We can now reference the variable we defined in other CSS rules.

:root {
  --primary-color: #fff;
  --background-color: #e5e5e5;
  --text-color: #2d2d2d;
}

body {
  background-color: var(--background-color);
  color: var(--text-color);
  font-family: Lato;
  padding: 0;
  margin: 0;
}

To use a custom property, we use the var keyword to pass in a reference to the custom property. In our example above we can set the body background and text colors to our defined custom properties. Next, we will look at our app-card component and how it uses our CSS variables.

Component CSS

Our first Angular component, the card component is relatively simple using ng-content to allow use to pass HTML content into the inner template.

import { Component } from '@angular/core';

@Component({
  selector: 'app-card',
  styleUrls: ['./card.component.css'],
  template: `
    <ng-content></ng-content>
  `
})
export class CardComponent {}

Let's now take a look at the CSS for the card component.

:host {
  display: block;
  padding: 12px;
  border-radius: 2px;
  background-color: var(--primary-color);
  color: var(--text-color);
}

The first style on the app-card component uses the :host selector. The :host selector will style the host element of our component. The host element is the element that is created for the component, so in our use case that will be app-card. We have some basic styles such as display, padding, and border-radius. Our card now uses our CSS Custom Properties we defined with the :root selector.

background-color: var(--primary-color);
color: var(--text-color);

We can see to use our CSS Custom Properties we use the var syntax. Now whenever we change the value of one of these properties in our application the card will reflect those changes. Next, let's take a look at the header component.

import { Component } from '@angular/core';

@Component({
  selector: 'app-header',
  template: `
    <p>
      header works!
    </p>
  `,
  styleUrls: ['./header.component.css']
})
export class HeaderComponent {}

Our app-header component is very basic so let's jump to the styles.

:host {
  display: block;
  padding: 4px 12px;
  background-color: var(--primary-color);
  color: var(--text-color);
}

Similar to our app-card component we can see the header references our two properties, --primary-color and --text-color. Now that we see how our Angular components use the CSS Custom Properties how do we update them? Next, we will look at the Theme Service we used earlier to toggle between the light and dark themes.

Theme Service

Our theme service is an Angular service that we have created to abstract out the logic for toggling the CSS Custom Properties. First, how do we set a CSS Custom Property from JavaScript?

document.documentElement.style.setProperty('--primary-color', '#455363');

Using JavaScript, we can easily set any custom property key and value. Our Angular Theme Service simply abstracts this into a single place for our App component to toggle.

import { Injectable } from '@angular/core';

export const darkTheme = {
  'primary-color': '#455363',
  'background-color': '#1f2935',
  'text-color': '#fff'
};

export const lightTheme = {
  'primary-color': '#fff',
  'background-color': '#e5e5e5',
  'text-color': '#2d2d2d'
};

@Injectable({ providedIn: 'root' })
export class ThemeService {
  toggleDark() {
    this.setTheme(darkTheme);
  }

  toggleLight() {
    this.setTheme(lightTheme);
  }

  private setTheme(theme: {}) {
    Object.keys(theme).forEach(k =>
      document.documentElement.style.setProperty(`--${k}`, theme[k])
    );
  }
}

Using Angular Components, Services and Custom CSS Properties we can quickly build out robust themable applications for our users. Check out the full working demo below!

View Demo Code   
Twitter Facebook LinkedIn Email
 

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

Angular

Creating Dynamic Tables in Angular

Learn how to easily create HTML tables in Angular from dynamic data sources.

Read Article
Web Components

Reusable Component Patterns - Default Slots

Learn about how to use default slots in Web Components for a more flexible API design.

Read Article
Web Components

Reusable Component Anti-Patterns - Semantic Obfuscation

Learn about UI Component API design and one of the common anti-patterns, Semantic Obfuscation.

Read Article