Metaquery and the end of media queries
A simpler approach to responsive CSS
Around about the 50th time you copy paste something like:
@media only screen and (min-width: 481px) and (max-width: 768px) {
/* TODO: literally CSS until you die */
};
you might start to think there should be a better way to manage breakpoints.
In case itโs not obvious why copy pasting snippets like the above is a bad idea, youโre propagating magic numbers, the chance of introducing errors is high (like two media queries that overlap or, worse, have a gap between them), and if you ever want to change breakpoints or introduce another, youโre literally going to die of CSS (not a pretty way to go).
Enter SASS
You might have been pretty excited to learn of SASS 3.2โs support for @content
blocks in mixins, and started doing the following:
@mixin respond-to($media) {
@if $media == small {
@media only screen and (max-width: 480px) { @content; }
}
@else if $media == medium {
@media only screen and (min-width: 481px) and (max-width: 768px) { @content; }
}
@else if $media == large {
@media only screen and (min-width: 769px) { @content; }
}
}
.wow-such-responsive {
@include respond-to(small) {
/* still */
}
@include respond-to(medium) {
/* not */
}
@include respond-to(large) {
/* great */
}
}
Thatโs a big improvement. Youโre now able to use meaningful words small
medium
and large
rather than magic pixel breakpoints, which massively reduces the chance of errors creeping through.
The output CSS isnโt ideal, though, with the lengthy @media
queries copied verbatim throughout the output. And, given the importance of order of style declarations in CSS, itโs not necessarily possible to merge them after generation.
Javascript wut
You might have tidied up your SASS using a mixin, but youโve not made any impact on JS-land. Youโre back to using strings again:
if (window.matchMedia("only screen and (min-width: 481px) and (max-width: 768px)").matches) {
/* yeah */
} else {
/* nah */
}
Media queries in JS arenโt super common, Iโll grant you, but this still sucks.
Metaquery
Ben Schwarz, who I share an office with and who is rad at the web, wrote a tiny library to treat media queries a little differently. The basic premise is to have a series of meta
tags define your breakpoints that get turned into breakpoint-X
class on your html
attribute automatically.
Define these in your head
:
<meta name="breakpoint" content="small"
media="only screen and (max-width: 480px)">
<meta name="breakpoint" content="medium"
media="only screen and (min-width: 481px) and (max-width: 768px)">
<meta name="breakpoint" content="large"
media="only screen and (min-width: 769px)">
And Metaquery will attach classes depending on which media query match:
<html class='breakpoint-small'>
Your CSS can then be based directly off the presence or absence of that class, rather than ever worrying about @media
:
.breakpoint-small .wow-such-responsive { /* much */ }
.breakpoint-medium .wow-such-responsive { /* simple */ }
.breakpoint-large .wow-such-responsive { /* wowe */ }
Even better with SASS:
.wow-such-responsive {
.breakpoint-small & {
/* such clear */
}
.breakpoint-medium & {
/* so reuse */
}
.breakpoint-large {
/* wow */
}
}
And your JS can use it too:
if (document.documentElement.classList.contains('breakpoint-small')) {
/* hashtag */
} else {
/* winning */
}
The pro way to use it
Default breakpoint for old IE
Metaquery relies on matchMedia which IE9 and below donโt have. So add a default desktop breakpoint on the HTML and wrap Metaquery in conditional IE comments.
There is a polyfill, but I donโt recommend it. Older browsers will do just fine with a static experience.
Load Metaquery before CSS
Something to watch out for when you have a default breakpoint or no breakpoint set: when the CSS is loaded, the browser will start laying out the screen incorrectly until Metaquery adds the right classes. So youโll probably get an unsightly FOUC (Flash of unstyled content, or in this case incorrectly-styled content).
Additionally, if youโre using different background images depending on breakpoint, the browser may download the wrong images first, then have to fetch the right ones. Thatโs bad. So you want to ensure Metaquery is loaded first. How?
Inline it!
Metaquery is so small, itโs faster to inline it into your HTML than serve it separately. That way itโll be one of the first things to execute, and should prevent any FOUC:
<!doctype html>
<html lang="en" class="breakpoint-medium">
<head>
<!-- Normal HEAD stuff here -->
<!-- Breakpoints -->
<meta name="breakpoint" content="small" media="(max-width: 480px)">
<meta name="breakpoint" content="medium" media="(min-width: 481px) and (768px)">
<meta name="breakpoint" content="large" media="(min-width: 769px)">
<!--[if gt IE 9]><!--><script type="text/javascript" charset="utf-8">
<!-- INLINE METAQUERY HERE -->
</script><!--<![endif]-->
<!-- Then link your stylesheets -->
<link rel="stylesheet" href="styles/main.css">
</head>
Get cracking!
Iโve got a full demo Gist showing a real copy-pastable boilerplate for getting going. Start your next HTML file with this and never write a @media
query again!