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://joor.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 6.2k readers and learn something new every week with Web Weekly.

