Want CSS variables in media query declarations? Try this!

2 minute readLast updated

If you're like me, you've probably been stretching CSS variables / custom properties to their limits while building your own design systems. But this "silver bullet" can lead to a nasty roadblock: you can't use them in media query declarations.

To clarify, this is the behavior you might be after:

:root {
	--mobile-breakpoint: 600px;
	--tablet-breakpoint: 900px;
}

@media(max-width: var(--mobile-breakpoint)) {
	.obligatory-hamburger-menu { visibility: visible }
}

...which sadly won't work 😣

This may seem confusing at first. If our variables are available at the :root of the page, why can't a media query access them?

Well, it comes down to what the :root element actually means: the root element of the HTML document. But conceptually, media queries aren't attached to HTML elements at all. These declarations are processed while your CSS is being parsed, so it won't know to look to the :root and pull in the variable values.

If this seems confusing, let's consider a CSS variable like this one on the body element:

body {
	--mobile-breakpoint: 600px;
}
@media(max-width: var(--mobile-breakpoint)) {...}

Clearly, our @media query doesn't know about this --mobile-breakpoint variable since it doesn't "belong" to the body element. Heck, it doesn't belong to any element in your HTML, which explains why our :root solution won't work either.

Sass / SASS can make this even more confusing by allowing @media blocks to be "nested" inside other rulesets. Don't be fooled! When your lovely Sass gets compiled, those media queries float right back out to the base of your CSS document.

As luck would have it, the W3C isn't happy about this limitation either. Their proposal for "environment" variables is at the earliest stage right now (phase 1 as of May 2021), but it seems to tackle this very issue!

I'll avoid showing you the syntax since it'll likely change overtime. Just know it's meant to address variables that exist beyond the HTML element level for use cases like @media declarations.

If you want media query variables and want them now, there's a few options available to you.

The first is what I'd recommend to existing Sass users: fall back to Sass variables for media queries, and use CSS variables everywhere else.

This is because, unlike CSS variables, Sass variables don't "belong" to CSS rulesets under-the-hood; They're just a piece of syntactical sugar for SASS to pick up and "paste" in the correct value. Just know that you'll need to fall back to Sass for math calculations as well instead of using the calc() function (because this isn't valid either!)

/* ✅ good */
@media(max-width: $mobile-breakpoint + 50px) {...}
/* ❌ bad */
@media(max-width: calc($mobile-breakpoint + 50px)) {...}

If you either don't use Sass or don't want 2 different syntaxes for your variables, there's a neat PostCSS plugin you can try too. Just tack this onto an existing CSS build step (or introduce a build step against your will 😣) and BAM! CSS variables and the CSS calc() function will work in your media query declarations 👍

⚠️ Word of caution on this solution though. You'll be writing invalid CSS by current browser standards and making it valid, which can look pretty confusing to new codebase contributors. At least be sure to document this plugin in your project README if you decide to go this route.


Learn a little something? Share this post on Twitter!

Hand drawn portrait of Ben Holmes
is a web developer + UI designer by trade from Charleston SC. He's been tinkering with JavaScript and CSS since 2014, and has lead a full stack bootcamp curriculum used by universities across the country. Now, he's working fulltime at Peloton and teaching the world a bit of web whimsy in his spare time.