CSS Variables in WebKit

CSS variables, properly known as CSS Custom Properties, were added to WebKit in the fall of 2015. WebKit has always been intrigued by the idea of putting variables in CSS, ever since 2008 when Dave Hyatt added our first experimental implementation. Since then there were two more re-writes of CSS Custom Properties, which helped shape the standard that browser vendors are settling on today.

CSS variables allow exactly what developers expect, assigning a value to a custom CSS symbol that can be re-used in the stylesheets of a web page. CSS quickly becomes complicated and often requires a lot of repetitive code, so the benefits of variables are obvious. Variables make it possible to reduce the amount of duplicate code and simplify the maintenance of complex CSS systems.

Developers have enjoyed variable-like features in their CSS for years using pre-processors as part of a web development tool chain. Unlike pre-processors though, native CSS variables bring dynamic variables to the mostly static syntax of CSS. CSS variables are, in fact, custom CSS properties that have DOM context. This is an important characteristic that allows CSS Custom Properties to do things you can’t do with variables in pre-processors. These are live properties that can mutate depending on the cascade of rules. Understanding this nuanced difference unlocks the full potential that CSS Custom Properties provides.

Custom Property Syntax

At first glance the syntax may seem a little strange compared to variable syntax in other languages. It is designed to fit within the existing CSS grammar using a shorthand prefix. To define a custom property, create a CSS element rule and within it add a custom property name with a double-dash prefix:

#foo {
   --default-color: #08c;
}

Generally, you’ll define the default values for custom properties using the :root pseudo-selector so that they are available everywhere:

:root {
   --default-color: #08c;
}

To use the variable property, use the var() function passing the custom property name and optionally a fallback value in cases where the custom property may not be available:

#foo h1 {
    color: var(--default-color, black);
}

Since the var() function produces a value, it can be used in place of any value, such as inside of other CSS functions like calc() or linear-gradient().

.gradient {
    width: 66%;
    height: 240px;
    background: linear-gradient(120deg, rgb(100, 0, var(--less-blue, 255)), rgb(100, 255, var(--more-blue, 0)));
}

.gradient:hover {
    --less-blue: 100;
    --more-blue: 255;
}
Hover over the gradients to apply the custom properties

In the above figure, the custom properties are applied to only one gradient at a time following the normal rules of :hover behavior.

CSS custom properties can also be accessed in JavaScript:

element.style.getPropertyValue('--less-blue');

It’s worth noting that the above JavaScript provides the inherited custom property value for element, not necessarily the value set in a :root pseudo-selector rule. To get a custom property value set on the root element, get the styles applied to document.documentElement:

var rootStyles = getComputedStyle(document.documentElement);
var defaultColor = rootStyles.getPropertyValue('--default-color');

Since these are runtime properties, values can be updated dynamically:

element.style.setProperty('--less-blue', '75');
document.documentElement.style.setProperty('--default-color', '#c80');

Simplifying Web Inspector

For a real world example, look no further than Web Inspector in WebKit where using CSS Custom Properties made it possible to simplify styling, trim some complex CSS rules, and make future maintenance easier. That’s a lot of wins that wouldn’t have been possible to the same degree using CSS pre-processor variables.

Being a web app, the interface of Web Inspector is driven by web technologies, but it’s designed to look and work like an app native to the OS. To maintain the user experience when Web Inspector is in a window, the native window behaviors need to be emulated. For example, when the window changes state from active to inactive the look of the window controls and frame elements need to simulate what a native inactive window would do — diminish the window’s visual impact by reducing contrast.

webkit-windows-active-inactive

To achieve this effect, the CSS of Web Inspector keys off of a window-inactive class with rules that override the normal appearance. The override rules change colors to reduce the contrast. Before CSS Custom Properties, there was no single-rule approach to override the colors on all of the properties for all of the elements that need adjusted. The approach consisted of a rule for an element that sets a normal active-state color along with an override rule when a window-inactive class is set on the document body to subtly change the color.

.sidebar.left {
    border-right: 1px solid hsl(0, 0%, 70%);
}

.sidebar.right {
    border-left: 1px solid hsl(0, 0%, 70%);
}

body.window-inactive .sidebar.left {
    border-right-color: hsl(0, 0%, 85%);
}

body.window-inactive .sidebar.right {
    border-left-color: hsl(0, 0%, 85%);
}

What CSS Custom Properties allows instead is one override to rule them all. The default state has an initial custom property definition, then changes the custom property value for the new window state directly:

:root {
    --border-color: hsl(0, 0%, 70%);
}

body.window-inactive { 
    --border-color: hsl(0, 0%, 85%); 
}

This allows inheritance to do all of the work so that element-specific state overrides can be dropped in favor of simply referencing the variable custom property.

.sidebar.left {
    border-right: 1px solid var(--border-color);
}

.sidebar.right {
    border-left: 1px solid var(--border-color);
}

The 4-line change to declare CSS variables in Web Inspector replaced 32 duplicated color values with var(--border-color) and eliminated 23 rules at the same time!

Takeaways

CSS Custom Properties unlock better techniques for architecting stylesheets that can dramatically reduce CSS code used, and make management of large CSS systems far easier. Controlling thematic color changes is just a starting point. Imagine the possibilities of cascading dynamic variables for managing fonts, spacing, backgrounds, element positions, or naming custom transition timing curves.

Feedback

You can try out CSS Custom Properties in the WebKit Nightly Build or in Safari 9.1 on iOS 9.3, and OS X El Capitan 10.11.4, Yosemite, and Mavericks. Let us know how you’re using CSS Custom Properties and send feedback on Twitter (@webkit, @jonathandavis) or by filing a bug.