Conditional CSS

I got some great comments on my post about conditionally loading content.

Just to recap, I was looking for a way of detecting from JavaScript whether media queries have been executed in CSS without duplicating my breakpoints. That bit is important: I’m not looking for MatchMedia, which involves making media queries in JavaScript. Instead I’m looking for some otherwise-useless CSS property that I can use to pass information to JavaScript.

Tantek initially suggested using good ol’ voice-family, which he has used for hacks in the past. But, alas, that unsupported property isn’t readable from JavaScript.

Then Tantek suggested that, whatever property I end up using, I could apply it to an element that’s never rendered: meta or perhaps head. I like that idea.

A number of people suggested using font-family, citing Foresight.js as prior art. I tried combining that idea with Tantek’s suggestion of using an invisible element:

@media screen and (min-width: 45em) {
    head {
        font-family: widescreen;
    }
}

Then I can read that in JavaScript:

window.getComputedStyle(document.head,null).getPropertyValue('font-family')

It works! …except in Opera. Where every other browser returns whatever string has been provided in the font-family declaration, Opera returns the font that ends up actually getting used (Times New Roman by default).

I guess I could just wait a little while for Opera to copy whatever Webkit browsers do. (Ooh! Controversial!)

Back to the drawing board.

Stephanie suggested using z-index. I wouldn’t to do that in the body of my document for fear of screwing up any complex positioning I’ve got going on, but I could apply that idea to the head or a meta element:

@media screen and (min-width: 45em) {
    head {
        z-index: 2;
    }
}

Alas, that doesn’t seem to work in Webkit; I just get back a value of auto. Curses! It works fine if it’s applied to an element in the body but like I said, I’d rather not screw around with the z-indexing of page elements. Ironically, it works fine in Opera

A number of people suggested using generated content! “But how’s that supposed to work?” I thought. “I won’t be able to reference the generated DOM node from my JavaScript, will I?”

It turns out that I’m an idiot. That second argument in the getComputedStyle method, which I always just blindly set to null, is there precisely so that you can access pseudo-elements like generated content.

Dave McDermid, Aaron T. Grogg, Colin Olan, Elwin Schmitz, Emil, and Andy Rossi arrived at the solution roundabout the same time.

Here’s Andy’s write-up and code. His version uses transition events to fire the getComputedStyle check: probably overkill for what I want to do, but very smart thinking.

Here’s Emil’s code. I was initially worried about putting unnecessary generated content into the DOM but the display:none he includes should make sure that it’s never seen (or read by screenreaders).

I could just generate the content on the body element:

@media all and (min-width: 45em) {
    body:after {
        content: 'widescreen';
        display: none;
    }
}

It isn’t visible, but it is readable from JavaScript:

var size = window.getComputedStyle(document.body,':after').getPropertyValue('content');

And with that, I can choose whether or not to load some secondary content into the page depending on the value returned:

if (size == 'widescreen') {
    // Load some more content.
}

Nice!

As to whether it’s an improvement over what I’m currently doing (testing for whether columns are floated or not) …probably. It certainly seems to maintain a nice separation between style and behaviour while at the same time allowing a variable in CSS to be read in JavaScript.

Thanks to everyone who responded. Much appreciated.

Update: If you’re finding that some browsers are including the quotes in the returned :after value, try changing if (size == 'widescreen') to if (size.indexOf("widescreen") !=-1). Thanks to multiple people who pointed this out.

Have you published a response to this? :

Responses

Emil Björklund

Yesterday, I stumbled across Eric Meyer’s post on displaying CSS breakpoint information with generated content. It reminded me of a solution to a related problem that Jeremy Keith blogged about way back in 2012 – how to conditionally load content (via JavaScript) when a certain breakpoint is active.

I was one of the people who came up with a potential solution to Jeremy’s problem, using pseudo-elements to stash a bit of text on (for example) the body element, and then reading that data via JS, using window.getComputedStyle(el, '::after') etc.

It was a hack, but it definitely worked.

Eric has discovered pretty much the exact same solution, but instead of hiding the stashed piece of text, he’s showing it to get a visual clue as to which media query (out of a few possible candidates) is currently active. Smart!

Now to the fun part: I remember having a discussion with Jeremy at some point about how the proper way to communicate in this manner between CSS & JS would be Custom Properties. I blogged about that back in 2013, using the Media Query naming technique as an example.

The solution I proposed there makes perfect sense as a small improvement to Eric’s debugging trick. Using a custom property allows for much cleaner code when setting the ”named breakpoint” text.

body::after { content: var(--bp, 'narrow');
} @media (min-width: 25em) { :root {--bp: '>=25em'}
}

The first use of the var() uses a fallback value that you can pass in if the --bp custom property is undefined in the current scope. Then, at each increasingly wide breakpoint, you could update the --bp value to something new.

I’ve put a quick demo on JSBin – wrapped in one of my favorite CSS one-liners which I first saw from Lea Verou (although I can’t remember exactly where):

@supports (--css: variables) {}

Ah, blogging. Not dead yet.

Related posts

Conditionally loading content

Conditional loading is a great technique for responsive designs but we need a better way of communicating between CSS and JavaScript.

Clean conditional loading

Tidying up some code I used in a 24 Ways article.

Related links

Responsive Components: a Solution to the Container Queries Problem — Philip Walton

Here’s a really smart approach to creating container queries today—it uses ResizeObserver to ensure that listening for size changes is nice and performant.

There’s a demo site you can play around with to see it in action.

While the strategy I outline in this post is production-ready, I see us as being still very much in the early stages of this space. As the web development community starts shifting its component design from viewport or device-oriented to container-oriented, I’m excited to see what possibilities and best practices emerge.

Tagged with

Comparing two ways to load non-critical CSS

Scott’s trying to find out the best ways to load critical CSS first and non-critical CSS later. Good discussion ensues.

Tagged with

Media Query Events Example

A page to demonstrate the conditional CSS technique I documented a while back.

Tagged with

filamentgroup/Southstreet

This is excellent! Scott, Wilto, and the gang at Filament Group have released the tools they use to help them craft performant responsive sites. Lots of excellent resources for conditional loading here.

Tagged with

An Ajax-Include Pattern for Modular Content | Filament Group, Inc., Boston, MA

Scott walks through the code and thinking behind the conditional loading pattern on The Boston Globe site. This is such a useful and valuable pattern!

Tagged with

Previously on this day

15 years ago I wrote Content First

In which I repeatedly hammer home the point that it’s all about the content.

17 years ago I wrote All Our Yesterdays

Opening up Bamboo Juice 2009.

19 years ago I wrote POSH Patterns

Not everything has to be a microformat.

19 years ago I wrote The Dunbar number of the beast

My invisible friend has a bigger Dunbar number than your invisible friend.

20 years ago I wrote Natural language hCard

You can use the hCard microformat in plain English sentences.

22 years ago I wrote Irish spring break

I’m back in Brighton after my short break in Ireland. For those of you uninterested in travelogues and holiday snaps, look away now.

23 years ago I wrote About this site

I’ve updated the "About" section of this site to include a new page about this site and how it was made.

24 years ago I wrote surRealpolitik

Ah, France.