Learn how to create dynamic styles with CSS custom properties and Angular’s style binding.
An exciting feature that came in Angular 9 is the ability to bind to CSS custom properties using style binding. Style binding lets us declare updates to an element’s style property in the component template, freeing the component class from implementing code for style manipulations.
The style property is an object that represents an element’s inline CSS declaration block. Most importantly, an inline declaration block can have a CSS property and CSS custom property declarations. We can update both the CSS property and the CSS custom property declarations at runtime to dynamically style elements.
So, what are CSS custom properties? And why would we use them when we can already dynamically style elements by updating CSS properties?
CSS custom properties are a relatively new feature of CSS that allows application developers to define their own CSS properties. CSS custom properties are used to hold values that can then be used in property declarations to style elements.
CSS custom properties offer exciting possibilities. To name a few, CSS custom properties:
You can read about the relationship between the HTML style attribute and DOM style property as well as the similarities and differences between CSS properties and CSS custom properties in my article on Understanding the DOM Style Property to Create Dynamic Styles.
In this article we will learn how to:
var()
function to access CSS custom property values in style rules@HostBinding
hsla()
and calc()
Let’s build a demo application that allows users to select the hue, saturation, lightness and opacity color values. We will create CSS custom properties to store these input values, then use them to style the application.
To create CSS custom properties, we need to declare them in a CSS declaration block. We will use style binding to declare the CSS custom properties inline and bind them to the input FormControl values.
Here is an example of the application in StackBlitz.
As mentioned earlier, from Angular V9, we can create and update inline CSS custom property declarations using style binding.
The syntax for binding to CSS custom properties is the same as for binding to CSS properties. There are two options:
<div [style.--css-custom-property-name]="template-expression"></div>
<div [style]="<template-expression>"></div>
The template expression should evaluate to a string containing the inline CSS declaration block.
Great! Let’s get started on our demo application by creating CSS custom property declarations to hold the input values for hue, saturation, lightness and opacity.
We could declare the CSS custom properties in individual bindings like so:
<div [style.--hue]="hue.value"
[style.--saturation.%]="saturation.value"
[style.--lightness.%]="lightness.value"
[style.--opacity]="opacity.value">
</div>
Or, alternatively, we could declare all the CSS custom properties in a single style binding using the newer syntax, as shown below:
@Component({
template: `
<div [style]="getStyles()"><div>`
})
export class HslaComponent {
// FormControl declarations go here
getStyles() {
return `--hue: ${this.hue.value},
--saturation: ${this.saturation.value},
--lightness: ${this.lightness.value},
--opacity: ${this.opacity.value}`;
}
}
The getStyles()
method returns the CSS declaration block with the CSS custom property declarations.
I am going to use individual property bindings, like in the first option, in this article.
The two dashes (--
) in front of the property names denote CSS custom properties.
We can make our code easy to read and maintain by giving the CSS custom properties descriptive names.
The style bindings above will do two things. Let’s look at these next.
The style bindings will create inline declarations for --hue
, --saturation
, --lightness
and --opacity
custom properties, setting their values to the respective FormControl
values.
Remember that a CSS declaration is a property and value pair.
The end result will be parallel to this:
<div style="--hue: 320;
--saturation: 100%;
--lightness: 50%;
--opacity: 1;">
</div>
if the FormControl
values were initialized as such:
class DemoComponent {
hue = new FormControl(320);
saturation = new FormControl(100);
lightness = new FormControl(50);
opacity = new FormControl(1);
}
Remember that the style property represents an element’s inline CSS declarations, so our CSS custom properties are declared inline on the div
element.
Secondly, the style binding will automatically update the CSS custom property values whenever the FormControl values change. That is, whenever the user changes the input value.
The saturation and lightness values in the hsla()
function need to be specified as percentages.
Instead of adding the percentage (%
) in our template expression, like so:
<div [style.--saturation]="saturation.value + '%'"
[style.--lightness]="lightness.value + '%'">
</div>
or adding it with the calc()
function:
.color-demo {
background-color: hsla(
var(--hue),
calc(var(--saturation) * 1%),
calc(var(--lightness) * 1%),
var(--opacity));
}
We can simply specify the unit as part of the style binding:
<div [style.--saturation.%]="saturation.value"
[style.--lightness.%]="lightness.value">
</div>
Style binding makes it super easy for us.
All right! Now that we have created CSS custom properties to hold the dynamic color input values, all we have to do is use these values in style rules.
Let’s see how to do that next.
We use the var()
function to access CSS custom property values in CSS property declarations.
Let’s style background-color
of our div
element to display the color selected by the user. It is still CSS properties that are used to style elements.
We have a couple of options for where to define the declaration:
style
attribute:<div style="background-color: hsla(
var(--hue),
var(--saturation),
var(--lightness),
var(--opacity);"
class="demo">
</div>
.demo {
width: 1em;
height: 1em;
border-radius: 50%;
background-color: hsla(
var(--hue),
var(--saturation),
var(--lightness),
var(--opacity)
);
}
The browser will substitute the var(<custom-property-name>)
functions to a computed value. The computed value is the CSS custom property value.
Resulting in something like this:
.demo { background-color: hsla(320, 100%, 50%, 1);}
Whenever the user input changes, the corresponding CSS custom property values will be updated through style binding. The var()
function will be substituted to the new value, setting the background-color
property accordingly.
For example, if the user changed hue
to 0
and saturation
to 80%
:
.demo { background-color: hsla(0, 80%, 50%, 1);}
We can pass a fallback value to the var()
function as the second argument.
.demo {
background-color: var(--primary-color, mintcream)
}
If the CSS custom property value is invalid or the CSS custom property is not in scope, the fallback value will be used.
The scope of a CSS custom property is the DOM element that it is declared on.
The CSS custom properties declared inline are scoped to that element. The CSS custom properties declared in stylesheets are scoped to DOM elements identified by the CSS selector in the style rule.
Just like CSS properties, CSS custom properties are inherited, so the children of the matching DOM elements also inherit the CSS custom properties.
We created the --hue
, --saturation
, --lightness
and --opacity
CSS custom property declarations inline on a div
element using style binding.
However, we may want to use these CSS custom properties to style other elements.
For example, it would be nice to display the selected hsla
values in text. Let us put the color div
and text in a container and style it with the user-selected color:
<div class="demo-container">
<div [style.--hue]="hue.value"
[style.--saturation.%]="saturation.value"
[style.--lightness.%]="lightness.value"
[style.--opacity]="opacity.value">
</div>
<p>
hsla({{hue.value}},
{{saturation.value}},
{{lightness.value}},
{{opacity.value}})
</p>
</div>
.demo-container {
border: 2px solid hsla(
var(--hue),
var(--saturation),
var(--lightness),
var(--opacity));
}
However, the parent div
does not have access to the --hue
, --saturation
, --lightness
and --opacity
CSS custom properties.
As we mentioned earlier, the scope of the CSS custom properties is the element that they are declared on. They are only available to that element and its children through inheritance.
We can increase the scope of our CSS custom properties to the main
element so all the elements in our component can access them:
<main [style.--hue]="hue.value"
[style.--saturation.%]="saturation.value"
[style.--lightness.%]="lightness.value"
[style.--opacity]="opacity.value">
<!--Input elements -->
<!--Color demo -->
</main>
As a side note, CSS custom properties are available to all elements, including semantic HTML elements like <main>
. So, we don’t need to create a <div>
wrapper in order to declare CSS custom properties.
Instead of composing the hsla function each time, let’s create a CSS custom property declaration to hold the hsla values. We can give it a descriptive name, like --user-selected-color
or --primary-color
:
main {
--primary-color: hsla(
var(--hue),
var(--saturation),
var(--lightness),
var(--opacity));
}
And use it each time we need the color:
.demo-container {
border: 2px solid var(--primary-color);
}
.demo-color {
background-color: var(--primary-color);
}
.demo-text {
color: var(--primary-color);
}
Note that we can still access the individual --hue
, --saturation
, --lightness
and --opacity
values if we wanted to. You’d recall that we defined them using style binding earlier.
What if we wanted to style our host element using the user’s selected color?
We can use @HostBinding()
to create and update CSS custom property declarations on our host component:
export class HslaComponent {
hue = new FormControl(1);
saturation = new FormControl(50);
lightness = new FormControl(50);
opacity = new FormControl(1);
@HostBinding('style.--primary-color')
public get hslaColor() {
return `hsla( ${this.hue.value},
${this.saturation.value}%,
${this.lightness.value}%,
${this.opacity.value})`;
}
}
We can then use --primary-color
in the style rules for the host:
:host {
display: block;
background-color: var(--primary-color);
}
As we mentioned earlier, the child elements inherit the CSS custom properties.
CSS custom properties can be used in the calc()
function.
To demonstrate the potential use, let’s create an accent color that is the complement of the user-selected color.
The complementary hue value is calculated by adding 180
degrees to the hue color value. Note, this calculation takes advantage of the fact that when the hue is larger than 360 degrees the calculated value is the number of degrees past 360.
Complement of 60
is 240
:
60 + 180 = 240
Complement of 240
is 60
:
240 + 180 = 420
, and the computed hue is 420 - 360 = 60
Let us give the CSS custom property a descriptive name, --accent-color
, and declare it on the main
element so it is available to all the elements in the component:
main {
--accent-color: hsla(
calc(var(--hue) + 180),
var(--saturation),
var(--lightness),
var(--opacity) );
}
select {
background-color: var(--accent-color);
}
The value assigned to --accent-color
has few parts to it:
--hue
, --saturation
, --lightness
and --opacity
CSS custom properties that hold the user-selected hsla
color values.hsla()
function.calc()
function to calculate the complement of the --hue
value.Although the explanation is long, the code is quite neat and compact!
Through the magic of style binding and CSS custom properties, each time the user changes the hsla values, our select element gets styled with its complementary color.
Similar to CSS preprocessor variables, CSS custom properties can be defined in one place and used in multiple places.
Let’s use the --primary-color
to style the select
element’s border
and add a box-shadow
on hover:
select {
border: 1px solid var(--primary-color);
}
select:hover {
box-shadow: 0 0 3px var(--primary-color);
}
We can assign --accent-color
or --primary-color
to any CSS properties that expect a color value.
Unlike CSS preprocessor variables, CSS custom properties have the added advantage of being dynamic.
Let us recap what we learned in this article.
CSS custom properties are an exciting feature of CSS that allow us to define our own CSS properties to hold style values. The var()
function lets us access these values in style rules.
We can dynamically style applications by manipulating CSS custom properties at runtime. However, this can also be achieved by manipulating the built-in CSS properties. So why would we be interested in CSS custom properties?
In addition to manipulating CSS custom properties to dynamically style our applications, we can use them in multiple CSS property declarations, assign them to other CSS custom properties and use them in CSS functions like hsla()
and calc()
.
Instead of implementing the code in our component classes to get references to view children and update their CSS custom properties, we can now use style binding and keep our component class lean.
From Angular 9, style binding includes binding to CSS custom properties as well as CSS properties making it really easy to use CSS custom properties for dynamic styling.
Below you can find an interactive StackBlitz example of an Angular clock with CSS custom properties.
https://www.w3.org/TR/css-variables
https://twitter.com/wnodom/status/1191137920570286080
https://stackblitz.com/edit/css-custom-prop-color-values?file=style.css
https://coryrylan.com/blog/theming-angular-apps-with-css-custom-properties
https://www.sitepoint.com/practical-guide-css-variables-custom-properties
https://www.smashingmagazine.com/2018/05/css-custom-properties-strategy-guide
https://www.smashingmagazine.com/2017/04/start-using-css-custom-properties
https://www.smashingmagazine.com/2019/07/css-custom-properties-cascade
https://una.im/css-color-theming/
https://codepen.io/MadeByMike/pen/mLNzdW
Ashnita is a frontend web developer who loves JavaScript and Angular. She is an organizer at GDGReading, a WomenTechmakers Ambassador and a mentor at freeCodeCampReading. Ashnita is passionate about learning and thinks that writing and sharing ideas are great ways of learning. Besides coding, she loves the outdoors and nature.