Why custom properties don't work with the url() CSS function
- Published at
- Updated at
- Reading time
- 3min
Custom properties and CSS parsing are always good for surprises like !important
behaving slightly differently or properties being "invalid at computed value time".
Today I discovered yet another surprise โ custom properties don't work in combination with the url()
function. ๐ฒ
.something {
/* this doesn't work */
--image: "https://jo.com/image.jpg";
background: url(var(--image));
}
Roman Dvornov describes the details quite well in a GitHub issue, but let me give you a condensed explanation.
Your url()
-containing CSS will be parsed differently, depending on how you use url()
. There's an old and a newer way:
- Old:
url(https://jo
.com/image .jpg) - Newer:
url('https://jo
or.com/image .jpg') url("https://jo
.com/image .jpg")
The problem of the legacy url()
token
And these missing quotes of the old way might seem like a tiny detail, but they affect how your CSS is parsed.
Without quotes, the url()
syntax looks like a CSS function, but it isn't. CSS parsers will treat it as a single token, a so-called url-token
.
.something {
background: url(https://ja.com/image.jpg);
// \---------------------------/
// without quotes this โ๏ธ is
// a single CSS token
}
And this entire token from url(
to the closing )
enforces parentheses, whitespace characters, single quotes (') and double quotes (") to be escaped with a backslash.
If you're curious, here's how url()
is parsed in this scenario.
url()
with a quoted string, on the other hand, is a normal and flexible CSS function notation, which is parsed part by part and works as expected.
But now, guess what happens when you want to use a custom property in combination with url()
?
.something {
/* this doesn't work */
--image: "https://jo.com/image.jpg";
background: url(var(--image));
// โ๏ธ "No quotes? Cool, that's a url-token!"
// ๐ข "Too bad though, `(` isn't allowed in here..."
// โ "I'll throw everything away!"
}
Because there are no quotes, this declaration is parsed as a url-token
. And unfortunately, the (
in var(--image)
isn't escaped, so the parser throws an error and invalidates the entire CSS declaration.
And this legacy url-token
parsing is why you can't use custom variables inside of url()
.
How can your work around the url-token
problem then?
First, you can restructure your code and move the url()
function into the custom property declaration itself. The following work just fine! ๐
.something {
--image: url(https://jo.com/image.jpg);
background: var(--image);
}
Additionally, the CSS spec maintainers added a new alias to remove the url-token
behavior. The src()
notation behaves the same way as url()
but without this weird legacy url-token
logic.
.something {
/* this works! (theoretically) */
--image: "https://jo.com/image.jpg";
background: src(var(--image));
}
Unfortunately, no browser supports src()
(I couldn't find browser support information but tested current Chrome, Safari and Firefox) yet, so it's time for us to wait. ๐คทโโ๏ธ
CSS parsing and custom properties โ always good for a surprise!
If you want to read more about this topic, here are some resources:
Join 5.2k readers and learn something new every week with Web Weekly.