CSS Custom Properties in Microsoft Edge
What are CSS Custom Properties?
SASS/LESS and other pre-processors have been offering variables in CSS for years, which is one of the reasons why, in an informal poll, ~75% of polled web developers incorporate these tools in their day to day workflow. However, the biggest downfall of these tools is that they are effectively a “find & replace” of the specified value. This means that the variables can’t be updated without needing to recompile the stylesheets.
Enter CSS Custom Properties (née CSS Variables). While Custom Properties enable the same fundamental use cases, they have the additional benefits of being fully cascaded, being interacted with via JavaScript, and not requiring the additional build step to work.
How to use Custom Properties
Here’s a practical example: setting up primary and secondary colors for your site.
:root { | |
--primary: #0B61A4; | |
--secondary: #25567B; | |
} | |
header { | |
background: var(--primary); | |
border-bottom: 2px solid var(--secondary); | |
} |
Let’s look at what is happening here, to declare a new custom property you precede a valid ident with two dashes. In our example, we are setting up our color scheme by creating custom properties for our --primary
and --secondary
colors. Then to utilize these properties, we need to reference them using the var()
function.
It’s worth noting that a custom property can store any valid CSS, so feel free to get creative with how you utilize them! For example, the following is a valid custom property:
--bg: rgb(calc(var(--r) + var(--modifier)), calc(var(--g) + var(--modifier)), calc(var(--b) + (var(--modifier) + 50))); |
Note: We utilized this methodology of color math extensively in our Custom Properties Pooch demo—check it out here!
Creating a fallback
A common use case for custom properties is in components. You may design a component, and you want to provide sensible defaults for all of your custom properties. Custom Properties follows the same pattern as other CSS values and allows you to set fallback values. Here’s an example:
body { | |
background: var(--primary, blue); | |
} |
In this case, if the --primary
custom property doesn’t exist when substitution occurs we’ll use blue
instead of transparent.
Note: This does not allow a fallback for a value that doesn’t work for the given property. For example:
div { | |
--primary: blue; | |
margin-top: var(--primary, 12px); | |
} |
This doesn’t work because blue
is not a valid value for margin-top
, but Custom Properties don’t know the syntax rules for properties they’re use in. All that matters is whether we have a value for --primary
or not. Since we do have a value, we substitute it in, try to parse margin-top: blue
, and discard it as invalid per normal CSS rules.
Custom property scoping
Custom Properties are scoped like other CSS properties, by harnessing the cascade. This is valuable when, for instance, a team might be collaborating to build components or aspects of a site. To illustrate, let’s take our previous example, and add in a component we’re building that also has its own --primary
custom property:
body { | |
--primary: blue; | |
background: var(--primary); | |
} | |
.my-component { | |
--primary: yellow; | |
background: var(--primary); | |
} |
This will result in a blue
background for the body and a yellow
background for the .my-component
.
Detecting support for Custom Properties
It’s important to remember that CSS is designed to fail silently, so if a browser doesn’t support Custom Properties, you’ll only be able to see this if your styles have visual implications. An easy way to progressively enhance your site with Custom Properties is to provide different styles to browsers that don’t support Custom Properties. You can do this by wrapping your custom property work in a @supports
declaration block.
body { | |
background: red; | |
} | |
@supports(--foo: blue) { | |
body { | |
background: green; | |
} | |
} |
It’s important to note that @supports
only checks that --foo
is syntactically correct, not that the property and value match what has been declared. For example, both the previous and following examples will result in the body having a green
background in browsers that support Custom Properties, even though the current value for --foo
is not false
.
:root { | |
--foo: true; | |
} | |
body { | |
background: red; | |
} | |
@supports(--foo: false) { | |
body { | |
background: green; | |
} | |
} |
Modifying a custom property via script
One of the primary benefits of native support for Custom Properties is the capability of dynamically modifying them via script. In order to do this you need to modify custom properties using the setProperty()
and getPropertyValue()
APIs. For example, to modify our --primary
color on .my-component
, we can do the following:
var myComponent = document.getElementsByClassName('my-component')[0]; | |
myComponent.style.setProperty('--primary', 'green'); |
Then, to retrieve the computed value of the custom property, you utilize getPropertyValue()
from getComputedStyle()
:
var myComponent = document.getElementsByClassName('my-component')[0]; | |
var cs = getComputedStyle(myComponent); | |
cs.getPropertyValue('--primary'); |
Animating a custom property
You can animate custom properties, but you can’t interpolate them. What exactly does it mean to be able to animate but not interpolate a custom property? Well, let’s take the following example:
Remember that you can store any valid ident in a custom property, so think of it as being stored as a string in an engine. The browser understands how to interpolate color: green
to color: blue
but it doesn’t know how to interpolate between “hello” and “world.” So, what does the browser do in the previous example? The browser takes the duration and assigns the possible values amongst the frames. For this example, that would result in a value of 50px
for 500ms
and 0
for the other 500ms
; this is referred to in the spec as doing a 50% flip.
But I want them to interpolate!
We do too! The CSS Houdini Task Force is actively working on ways to make this happen with the Properties & Values API. If this work pans out, you’ll be able to register your custom property and inform the engine of its type, so that it can correctly interpolate values. Keep a look out for this API to start making its way into the wild, as browsers begin to experiment with these APIs to test their viability and help evolve the specification.
Improvements beyond Custom Properties
While working on our implementation of Custom Properties, we looked at a few demos that web developers had built. While some of them worked, others didn’t. When we reduced the broken demos to look for bugs, we narrowed the problems down to other gaps in our engine – not necessarily as a result of bugs in our Custom Properties implementation. For example, the largest gap we found was support for calc()
within color functions (eg: rgb()
, hsl()
). Because this is an exciting use case where custom properties can be very powerful, we addressed this issue in our parser.
“A rising tide lifts all boats”
As we worked to fill gaps in our engine, we also wanted to be sure that web developers can rely on interoperable implementations for the feature to be useful. As we began our implementation efforts, we found that no browser had implemented the 50% flip behavior suggested in the specification for animating custom properties. Additionally, we found that no browser supported the capability of referencing variables via the var()
function, within a keyframe at-rule. We worked with the CSSWG to resolve upon these issues and provide an implementation in Microsoft Edge.
Of course, the work in Edge is only one part of making sure the feature is interoperable. As we worked on our implementation, we found bugs in the implementations in existing browsers. As a result of this, we opened bugs against Blink, Gecko, and WebKit, and look forward to improved implementations across the board – including fixes shipping as early as Chrome 56 that will make Custom Properties more interoperable.
–in: “conclusion”
CSS Custom Properties not only solve a common developer request and makes your code more manageable, but it also unlocks a lot of potential for more creative work on the web. The future of the web looks bright and we’re just getting started. We’re excited to have CSS Custom Properties in the Windows 10 Creators Update, and can’t wait to see what you create!
― Greg Whitworth, Program Manager, Microsoft Edge