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.

Previously on this day

9 years ago I wrote Content First

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

11 years ago I wrote All Our Yesterdays

Opening up Bamboo Juice 2009.

13 years ago I wrote POSH Patterns

Not everything has to be a microformat.

13 years ago I wrote The Dunbar number of the beast

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

14 years ago I wrote Natural language hCard

You can use the hCard microformat in plain English sentences.

16 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.

17 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.

18 years ago I wrote surRealpolitik

Ah, France.