In CSS, some properties have shorthand. One property that takes separated values. Syntactic sugar, as they say, to make authoring easier. Take transition
, which might look something like:
.element {
transition: border 0.2s ease-in-out;
}
We could have written it like this:
.element {
transition-property: border;
transition-duration: 0.2s;
transition-timing-function: ease-in-out;
}
Every “part” of the shorthand value has its own property it maps to. But that’s not true for everything. Take box-shadow
:
.element {
box-shadow: 0 0 10px #333;
}
That’s not shorthand for other properties. There is no box-shadow-color
or box-shadow-offset
.
That’s where Custom Properties come to save us!
We could set it up like this:
:root {
--box-shadow-offset-x: 10px;
--box-shadow-offset-y: 2px;
--box-shadow-blur: 5px;
--box-shadow-spread: 0;
--box-shadow-color: #333;
}
.element {
box-shadow:
var(--box-shadow-offset-x)
var(--box-shadow-offset-y)
var(--box-shadow-blur)
var(--box-shadow-spread)
var(--box-shadow-color);
}
A bit verbose, perhaps, but gets the job done.
Now that we’ve done that, remember we get some uniquely cool things:
- We can change individual values with JavaScript. Like:
document.documentElement.style.setProperty("--box-shadow-color", "green");
- Use the cascade, if we need to. If we set
--box-shadow-color: blue
on any selector more specific than the :root, we’ll override that color.
Fallbacks are possible too, in case the variable isn’t set at all:
.element {
box-shadow:
var(--box-shadow-offset-x, 0)
var(--box-shadow-offset-y, 0)
var(--box-shadow-blur, 5px)
var(--box-shadow-spread, 0)
var(--box-shadow-color, black);
}
How about transforms? They are fun because they take a space-separated list of values, so each of them could be a custom property:
:root {
--transform_1: scale(2);
--transform_2: rotate(10deg);
}
.element{
transform: var(--transform_1) var(--transform_2);
}
What about elements that do have individual properties for their shorthand, but also offer comma-separated multiple values? Another great use-case:
:root {
--bgImage: url(basic_map.svg);
--image_1_position: 50px 20px;
--image_2_position: bottom right;
}
.element {
background:
var(--bgImage) no-repeat var(--image_1_position),
var(--bgImage) no-repeat var(--image_2_position);
}
Or transitions?
:root {
--transition_1_property: border;
--transition_1_duration: 0.2s;
--transition_1_timing_function: ease;
--transition_2_property: background;
--transition_2_duration: 1s;
--transition_2_timing_function: ease-in-out;
}
.element {
transition:
var(--transition_1_property)
var(--transition_1_duration)
var(--transition_1_timing_function),
var(--transition_2_property)
var(--transition_2_duration)
var(--transition_2_timing_function),
}
Dan Wilson recently used this kind of thing with animations to show how it’s possible to pause individual animations!
Here’s browser support:
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
49 | 31 | No | 16 | 10 |
Mobile / Tablet
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
126 | 127 | 126 | 10.0-10.2 |
I’m not sure I would go so far as to define each value of something like
box-shadow
separately. The way I’ve been doing it is something along the lines of:--boxshadow_lg: 0 8px 6px -6px rgba(0,0,0,.15);
--boxshadow_sm: 0 2px 3px -3px rgba(0,0,0,.15);
And then I can call these via
box-shadow: var(--boxshadow_lg);
and be done with it. There are certainly elements that I could change to a variable to further reduce redundancy but this seems a pretty efficient way to get enough of a mix of ‘easy to update’ and ‘simple to call out’Perhaps not!
But, that’s kind of the whole point here. You literally can’t set `box-shadow-color**, but with CSS Custom Properties, you kinda can.
Caniuse says that FireFox for Android does have support. Firefox for iPhone, of course, uses WebKit so if Mobil Safari has support, FF should have it there too.
Genius!
Hi Chris, is it possible (or do you have some ideas on) to call the variable from HTML attribute’s call? Something like this:
root: {
--align-left: left;
--align-center: center;
--align-right: right;
}
p {
text-align: var('--align-' attr(data-align));
}
No. That’s also a bad idea, since you’d be mixing content and presentation. There’s the reason why the
align
attribute has been deprecated.You can do a few things with
attr()
and variables, as currently supported in browsers (whereattr()
always returns a string).There are even more things that you could do, in theory, if the full CSS 3
attr()
function was supported (allowing you to tell the browser to parse the attribute value as a color, length, url, or other data type).Neither of them look like your code, however. You can’t compose the variable name on the fly.
var()
can only change the values.What you can do, now, is set variables on an element in a
style
attribute, and use those to trigger coordinate style changes from your stylesheet.Great write up! I think Dan Wilson beat you to it though in his article here
oops I just noticed that you already linked there
How I handled dynamic CSS in the past:
I’m using
<style class="dynamic-css">
in the<head>
and dynamically create it’s content concatenating strings in JavaScript. I can have “fallbacks” too, I just write them in my regular style sheet.This is even more browser compatible, as it relies on techniques used for ages. I admit that
setProperty()
is a convenient way of setting CSS properties, but at the price of still lacking browser support, especially when it comes to mobile.so….how do i use it with scss again?
So, custom properties are nothing but an ability to define variables and then use those variable else were in your css file.
Nice one Chris!