๐Ÿงข">
Published at
Updated at
Reading time
3min
This post is part of my Today I learned series in which I share all my web development learnings.

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.

The two modes of url()

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.com/image.jpg') or 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.

Parsing algorithm for a url-token

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().

The solution to work around the legacy 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:

If you enjoyed this article...

Join 6.2k readers and learn something new every week with Web Weekly.

Web Weekly โ€” Your friendly Web Dev newsletter
Reply to this post and share your thoughts via good old email.
Stefan standing in the park in front of a green background

About Stefan Judis

Frontend nerd with over ten years of experience, freelance dev, "Today I Learned" blogger, conference speaker, and Open Source maintainer.

Related Topics

Related Articles