Using CSS Variables in Your Angular Applications

CSS variables are not anything new in the CSS world, but they are a powerful and flexible way to centralize and manage styling properties, provide consistency, make maintenance easier, and give dynamic control over visual elements across an entire web application.

In this blog, I will show you how CSS variables work, and give you options for how to best incorporate them into an Angular application.

How CSS Variables Work

The CSS custom properties for cascading variables (CSS variables) module adds support for cascading variables in CSS properties and lets you create custom properties to define these variables along with the mechanisms to use custom properties as the values for other CSS properties. It has had near-universal support in browsers for quite some time.

CSS variables, also known as custom properties (--*) follow a specific format: they start with a two-dash prefix (--) and then a name. Here is an example: 

--my-first-css-variable: red;

The most common place to define these variables is on the :root element:

:root {
  --app-main-color: #000;
  --app-main-font-size: 14px;
}

After adding variables at this level, you can use them everywhere inside your application. To use the variable elsewhere in your CSS, you simply need to use the var() function wrapped around it like this:

body {
  color: var(--app-main-color);
  font-size: var(--app-main-font-size);
}

Simple Example with CSS Variables

Now let’s imagine that we have the HTML below, and we need to use another text color or text size for a specific element. With CSS variables, it’s easy to implement.

example.html 
<p>This block uses main color and font-size</p>
<p class="specific-styles-container">This block uses specified color and font-size</p>
example.css 
:root {
  --app-main-text-color: #000;
  --app-main-font-size: 16px;
}
p {
  color: var(--app-main-text-color);
  font-size: var(--app-main-font-size);
}
p.specific-styles-container {
  --app-main-text-color: red;
  --app-main-font-size: 20px;
}

Because the color and font-size values for the specific-styles-container class were changed, they show up differently from the default style for a P tag.

CSS Variables example 1

CSS vs SCSS Variables

Don’t worry if you use style preprocessors like SCSS, LESS, etc. They are all compatible with CSS variables. I will be using SCSS in the examples below.

SCSS offers its own variables, but CSS variables will help you in some cases. For example, they can help with facilitating style adjustments for child components without relying on deprecated methods like ::ng-deep and aiding in creating responsive layouts. Additionally, CSS variables play a crucial role in customizing Angular Material components, a feature introduced in Angular Material v15.

CSS variables remain invaluable in several scenarios, even when employing SCSS variables:

  1. Dynamic Styling for HTML Blocks: CSS variables prove indispensable when implementing dynamic styles for HTML elements. These styles may need to adapt based on user interactions, application state, or other dynamic factors.
  2. Calculated Data, like Width or Height: Utilizing CSS variables becomes essential when incorporating calculated data into stylesheets, such as dynamically adjusting width or height based on varying conditions or content.
  3. Dynamic Styles from External Sources, such as APIs: CSS variables provide a robust solution for incorporating dynamic styles sourced from external data, like APIs. For instance, color schemes for elements might depend on parameters fetched from an API response, enabling dynamic and customizable UIs.
  4. Avoiding ViewEncapsulation.None: In scenarios where it’s undesirable or impractical to use ViewEncapsulation.None for a component, CSS variables offer an alternative solution. They enable the encapsulation of styles within components while still allowing for dynamic adjustments and customization through variables.

By leveraging CSS variables alongside SCSS, developers can achieve greater flexibility and maintainability in their Angular applications, catering to diverse styling requirements and dynamic design needs.

Now that you know the basics of how to use CSS variables in general, let’s move on to the more interesting part where we will consider examples of using CSS variables in Angular applications.

Using CSS Variables in an Angular App

I propose to share an example where we will change styles for buttons using CSS variables. So let’s prepare some wrapper for the buttons.

styles.scss 
:root {
  --app-font-family: Arial;
  --app-font-size: 14px;
  --app-text-color: #000;
  --border-color: #424242
}

* {
  box-sizing: border-box;
}

body {
  font-family: var(--app-font-family);
  font-size: var(--app-font-size);
  color: var(--app-text-color);
  margin: 0;
}

h3 {
  margin: 0;
} 
app.component.ts 
import { CommonModule } from '@angular/common';
import { Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';

import { ButtonComponent } from './components/button/button.component';
import { IStyleProp, StylePropertyDirective } from './directives/style-property.directive';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    CommonModule,
    RouterOutlet,
    ButtonComponent,
    StylePropertyDirective
  ],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
  encapsulation: ViewEncapsulation.None
})
export class AppComponent {
  exampleStyleConfig: IStyleProp[] = [
    {name: '--app-button-border-width', value: '2px'},
    {
      name: '--app-button-hover-background-color',
      value: 'var(--app-button-background-color)'
    },
    {
      name: '--app-button-border-color',
      value: 'var(--app-button-background-color)'
    },
    {name: '--app-button-background-color', value: '#0ccbcb'},
    {name: '--app-text-color', value: '#682185'},
    {name: '--app-button-hover-text-color', value: '#feff00'}
  ];
}
app.component.html
<div class="container">
  <div class="btn-container">
    <h3>Default Button</h3>
    <app-button text="Click Me"></app-button>
  </div>

  <div class="btn-container">
    <h3>Example 1. Customized button styles via CSS variables.</h3>
    <app-button class="customized-btn1" text="Click Me"></app-button>
  </div>

  <div class="btn-container">
    <h3>Example 2. Customized button styles via CSS variables.</h3>
    <app-button class="customized-btn2" text="Click Me"></app-button>
  </div>

  <div class="btn-container">
    <h3>
      Example 3. Customized button styles via CSS variables, inline styles.
    </h3>
    <app-button class="customized-btn3"
                text="Click Me"
                [ngStyle]="{
                  '--app-button-border-width': '3px',
                  '--app-button-border-style': 'dashed'
                }">
    </app-button>
  </div>

  <div class="btn-container">
    <h3>
      Example 4. Customized button styles via CSS variables and directive
    </h3>
    <app-button class="customized-btn3"
                [appStyleProperty]="exampleStyleConfig"
                text="Click Me">
    </app-button>
  </div>
</div>
app.component.scss
.container {
  padding: 30px 20px;

  & > *:not(last-child) {
    margin-bottom: 20px;
  }

  .btn-container {
    border: 1px solid var(--border-color);
    border-radius: 5px;
    padding: 15px;

    h3 {
      margin-bottom: 10px;
    }
  }

  .customized-btn1 {
    --app-button-border-color: #0101b9;
    --app-button-background-color: #3c3ce3;
    --app-button-hover-background-color: #6e6ef5;
    --app-text-color: #fff;
  }

  .customized-btn2 {
    --app-button-border-width: 2px;
    --app-button-border-color: #fa0000;
    --app-button-hover-background-color: transparent;
    --app-button-hover-border-color: #ef9393;
    --app-text-color: #fa0000;
    --app-button-hover-text-color: #ef9393;
    --app-button-text-transform: uppercase;
  }
}

So far this is nothing special. I just prepared a wrapper with styles and added CSS variables defined globally in the :root element.

Now let’s create a button component in Angular:

button.component.ts 
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-button',
  standalone: true,
  templateUrl: './button.component.html',
  styleUrl: './button.component.scss'
})
export class ButtonComponent {
  /** Text for the button. Required */
  @Input() text!: string;
  /** Make button disabled. Optional */
  @Input() disabled?: string;
  /** Emit click action */
  @Output() clickAction = new EventEmitter<MouseEvent>();
}

Example 1: Initialize variables inside the :host element.

button.component.scss
:host {
  --app-button-border-width: 1px;
  --app-button-border-style: solid;
  --app-button-border-color: var(--border-color); // --border-color already in initialized for :root element
  --app-button-border-radius: 5px;
  --app-button-background-color: transparent;
  --app-button-hover-background-color: #cecece;
  --app-button-hover-border-color: var(--app-button-border-color);
  --app-button-hover-text-color: var(--app-text-color);
  --app-button-padding: 5px 10px;
  --app-button-text-transform: capitalize;
  --app-button-height: 36px;
}
.app-button {
  border: var(--app-button-border-width) var(--app-button-border-style) var(--app-button-border-color);
  border-radius: var(--app-button-border-radius);
  background-color: var(--app-button-background-color);
  padding: var(--app-button-padding);
  text-transform: var(--app-button-text-transform);
  height: var(--app-button-height);
  color: var(--app-text-color);
  transition: all 0.25s;

  &:hover {
    background-color: var(--app-button-hover-background-color);
    border-color: var(--app-button-hover-border-color);
    color: var(--app-button-hover-text-color);
  }
}

Example 2: Use initial value of the property as a fallback. If the variable is not initialized, the default value will be applied. I like this approach, so will use this variant in my examples below.

button.component.scss
.app-button {
  --_app-button-border-color: var(--app-button-border-color, var(--border-color));
  border: var(--app-button-border-width, 1px) var(--app-button-border-style, solid) var(--_app-button-border-color);
  border-radius: var(--app-button-border-radius, 5px);
  background-color: var(--app-button-background-color, transparent);
  padding: var(--app-button-padding, 5px 10px);
  text-transform: var(--app-button-text-transform, capitalize);
  height: var(--app-button-height, 36px);
  color: var(--app-text-color);
  transition: all 0.25s;

  &:hover {
    --app-button-background-color: var(--app-button-hover-background-color, #cecece);
    border-color: var(--app-button-hover-border-color, var(--_app-button-border-color));
    color: var(--app-button-hover-text-color, var(--app-text-color));
  }
}

One other possibility is customizing the styles via a directive.

style-property.directive.ts
import { Directive, ElementRef, inject, Input, OnChanges, SimpleChanges } from '@angular/core';

export interface IStyleProp {
  name: string;
  value: string | number;
}

@Directive({
  selector: '[appStyleProperty]',
  standalone: true
})
export class StylePropertyDirective implements OnChanges {
  @Input() appStyleProperty: IStyleProp | IStyleProp[] | null | undefined;

  private _elRef: ElementRef = inject(ElementRef);

  ngOnChanges(changes: SimpleChanges) {
    if (changes.appStyleProperty) {
      this._setProperty([].concat(this.appStyleProperty ?? []));
    }
  }

  private _setProperty(props: Array<IStyleProp>): void {
    if (
      !Array.isArray(props)
      || !props.length
      || !this._elRef?.nativeElement?.style
    ) {
      return;
    }

    props.forEach(({name, value}) => {
      if (!name && !value) {
        return;
      }

      this._elRef.nativeElement.style.setProperty(name, value);
    });
  }
}

As a result, we see four customized buttons and a default button, all styled with CSS variables, but using different customization methods:

  1. A default button that uses default styles.
  2. Example 1 and Example 2 use customization of styles using CSS classes .customized-btn1.customized-btn2. See app.component.scss.
  3. Example 3 uses customizing styles using HTML inline styles. See app.component.html.
  4. Example 4 uses customizing styles using the Angular attribute directive. See style-property.directive.tsapp.component.htmlapp.component.ts

Conclusion

We’ve looked at different options for customizing styles using CSS variables, such as via a CSS class, an inline style, and an Angular directive. As you can see, none of these options are hard to use, and they can all help you make your Angular component styles more flexible and readable.

Free Consultation

Sign up for a FREE consultation with one of Trailhead's experts.

"*" indicates required fields

This field is for validation purposes and should be left unchanged.

Related Blog Posts

We hope you’ve found this to be helpful and are walking away with some new, useful insights. If you want to learn more, here are a couple of related articles that others also usually find to be interesting:

Our Gear Is Packed and We're Excited to Explore With You

Ready to come with us? 

Together, we can map your company’s software journey and start down the right trails. If you’re set to take the first step, simply fill out our contact form. We’ll be in touch quickly – and you’ll have a partner who is ready to help your company take the next step on its software journey. 

We can’t wait to hear from you! 

Main Contact

This field is for validation purposes and should be left unchanged.

Together, we can map your company’s tech journey and start down the trails. If you’re set to take the first step, simply fill out the form below. We’ll be in touch – and you’ll have a partner who cares about you and your company. 

We can’t wait to hear from you! 

Montage Portal

Montage Furniture Services provides furniture protection plans and claims processing services to a wide selection of furniture retailers and consumers.

Project Background

Montage was looking to build a new web portal for both Retailers and Consumers, which would integrate with Dynamics CRM and other legacy systems. The portal needed to be multi tenant and support branding and configuration for different Retailers. Trailhead architected the new Montage Platform, including the Portal and all of it’s back end integrations, did the UI/UX and then delivered the new system, along with enhancements to DevOps and processes.

Logistics

We’ve logged countless miles exploring the tech world. In doing so, we gained the experience that enables us to deliver your unique software and systems architecture needs. Our team of seasoned tech vets can provide you with:

Custom App and Software Development

We collaborate with you throughout the entire process because your customized tech should fit your needs, not just those of other clients.

Cloud and Mobile Applications

The modern world demands versatile technology, and this is exactly what your mobile and cloud-based apps will give you.

User Experience and Interface (UX/UI) Design

We want your end users to have optimal experiences with tech that is highly intuitive and responsive.

DevOps

This combination of Agile software development and IT operations provides you with high-quality software at reduced cost, time, and risk.

Trailhead stepped into a challenging project – building our new web architecture and redeveloping our portals at the same time the business was migrating from a legacy system to our new CRM solution. They were able to not only significantly improve our web development architecture but our development and deployment processes as well as the functionality and performance of our portals. The feedback from customers has been overwhelmingly positive. Trailhead has proven themselves to be a valuable partner.

– BOB DOERKSEN, Vice President of Technology Services
at Montage Furniture Services

Technologies Used

When you hit the trails, it is essential to bring appropriate gear. The same holds true for your digital technology needs. That’s why Trailhead builds custom solutions on trusted platforms like .NET, Angular, React, and Xamarin.

Expertise

We partner with businesses who need intuitive custom software, responsive mobile applications, and advanced cloud technologies. And our extensive experience in the tech field allows us to help you map out the right path for all your digital technology needs.

  • Project Management
  • Architecture
  • Web App Development
  • Cloud Development
  • DevOps
  • Process Improvements
  • Legacy System Integration
  • UI Design
  • Manual QA
  • Back end/API/Database development

We partner with businesses who need intuitive custom software, responsive mobile applications, and advanced cloud technologies. And our extensive experience in the tech field allows us to help you map out the right path for all your digital technology needs.

Our Gear Is Packed and We're Excited to Explore with You

Ready to come with us? 

Together, we can map your company’s tech journey and start down the trails. If you’re set to take the first step, simply fill out the contact form. We’ll be in touch – and you’ll have a partner who cares about you and your company. 

We can’t wait to hear from you! 

Thank you for reaching out.

You’ll be getting an email from our team shortly. If you need immediate assistance, please call (616) 371-1037.