Redesigning the Media Query

Responsive web design is showing us that designing content is more important than designing containers. But if you’ve given RWD a serious try, you know that shifting your focus from the container is surprisingly hard to do. There are many factors and
instincts working against you, and one culprit is a perpetrator you’d least suspect.

The media query is the ringmaster of responsive design. It lets us establish the rules of the game and gives us what we need most: control. However, like some kind of evil double agent, the media query is actually working against you.

Its very nature diverts your attention away from content and forces you to focus on the container.

The very act of choosing a media query value means choosing a screen size.

Look at the history of the media query—it’s always been about the container. Values like screen, print, handheld and tv don’t have anything to do with content. The modern media query lets us choose screen dimensions, which is great because it makes RWD possible. But it’s still the act of choosing something that is completely unpredictable.

Content should dictate our breakpoints, not the container. In order to get our focus back to the only thing that matters, we need a reengineered media query—one that frees us from thinking about screen dimensions. A media query that works for your content, not the window. Fortunately, Sass 3.2 is ready and willing to take on this challenge.

Thinking in Columns

Fluid grids never clicked for me. I feel so disoriented and confused by their squishiness. Responsive design demands their use though, right?

I was ready to surrender until I found a grid that turned my world upright again. The Frameless Grid by Joni Korpi demonstrates that column and gutter sizes can stay fixed. As the screen size changes, you simply add or remove columns to accommodate. This made sense to me and armed with this concept I was able to give Sass the first component it needs to rewrite the media query: fixed column and gutter size variables.

$grid-column: 60px;
$grid-gutter: 20px;

We’re going to want some resolution independence too, so let’s create a function that converts those nasty pixel values into ems.

@function em($px, $base: $base-font-size) {
	@return ($px / $base) * 1em;
}

We now have the components needed to figure out the width of multiple columns in ems. Let’s put them together in a function that will take any number of columns and return the fixed width value of their size.

@function fixed($col) {
	@return $col * em($grid-column + $grid-gutter)
}

With the math in place we can now write a mixin that takes a column count as a parameter, then generates the perfect media query necessary to fit that number of columns on the screen. We can also build in some left and right margin for our layout by adding an additional gutter value (remembering that we already have one gutter built into our fixed function).

@mixin breakpoint($min) {
	@media (min-width: fixed($min) + em($grid-gutter)) {
		@content
	}
}

And, just like that, we’ve rewritten the media query. Instead of picking a minimum screen size for our layout, we can simply determine the number of columns needed. Let’s add a wrapper class so that we can center our content on the screen.

@mixin breakpoint($min) {
    @media (min-width: fixed($min) + em($grid-gutter)) {
	.wrapper {
		width: fixed($min) - em($grid-gutter);
		margin-left: auto; margin-right: auto;
	}
	@content
    }
}

Designing content with a column count gives us nice, easy, whole numbers to work with. Sizing content, sidebars or widgets is now as simple as specifying a single-digit number.

@include breakpoint(8) {
	.main { width: fixed(5); }
	.sidebar { width: fixed(3); }
}

Those four lines of Sass just created a responsive layout for us. When the screen is big enough to fit eight columns, it will trigger a fixed width layout. And give widths to our main content and sidebar. The following is the outputted CSS…

@media (min-width: 41.25em) {
  .wrapper {
    width: 38.75em;
    margin-left: auto; margin-right: auto;
  }
  .main { width: 25em; }
  .sidebar { width: 15em; }
}

Demo

I’ve created a Codepen demo that demonstrates what we’ve covered so far. I’ve added to the demo some grid classes based on Griddle by Nicolas Gallagher to create a floatless layout. I’ve also added a CSS gradient overlay to help you visualize columns. Try changing the column variable sizes or the breakpoint includes to see how the layout reacts to different screen sizes.

Responsive Images

Responsive images are a serious problem, but I’m excited to see the community talk so passionately about a solution. Now, there are some excellent stopgaps while we wait for something official, but these solutions require you to mirror your breakpoints in JavaScript or HTML. This poses a serious problem for my Sass-generated media queries, because I have no idea what the real values of my breakpoints are anymore. For responsive images to work, JavaScript needs to recognize which media query is active so that proper images can be loaded for that layout.

What I need is a way to label my breakpoints. Fortunately, people much smarter than I have figured this out. Jeremy Keith devised a labeling method by using CSS-generated content as the storage method for breakpoint labels. We can use this technique in our breakpoint mixin by passing a label as another argument.

@include breakpoint(8, 'desktop') { /* styles */ }

Sass can take that label and use it when writing the corresponding media query. We just need to slightly modify our breakpoint mixin.

@mixin breakpoint($min, $label) {
    @media (min-width: fixed($min) + em($grid-gutter)) {

        // label our mq with CSS generated content
	body::before { content: $label; display: none; }

	.wrapper {
		width: fixed($min) - em($grid-gutter);
		margin-left: auto; margin-right: auto;
	}
	@content
    }
}

This allows us to label our breakpoints with a user-friendly string. Now that our media queries are defined and labeled, we just need JavaScript to step in and read which label is active.

// get css generated label for active media query
var label = getComputedStyle(document.body, '::before')['content'];

JavaScript now knows which layout is active by reading the label in the current media query—we just need to match that label to an image. I prefer to store references to different image sizes as data attributes on my image tag.

<img class="responsive-image" data-mobile="mobile.jpg" data-desktop="desktop.jpg" />
<noscript><img src="desktop.jpg" /></noscript>

These data attributes have names that match the labels set in my CSS. So while there is some duplication going on, setting a keyword like ‘tablet’ in two places is much easier than hardcoding media query values. With matching labels in CSS and HTML our script can marry the two and load the right sized image for our layout.

// get css generated label for active media query
var label = getComputedStyle(document.body, '::before')['content'];

// select image
var $image = $('.responsive-image');

// create source from data attribute
$image.attr('src', $image.data(label));

Demo

With some slight additions to our previous Codepen demo you can see this responsive image technique in action. While the above JavaScript will work it is not nearly robust enough for production so the demo uses a jQuery plugin that can accomodate multiple images, reloading on screen resize and fallbacks if something doesn’t match up.

Creating a Framework

This media query mixin and responsive image JavaScript are the center piece of a front end framework I use to develop websites. It’s a fluid, mobile first foundation that uses the breakpoint mixin to structure fixed width layouts for tablet and desktop. Significant effort was focused on making this framework completely cross-browser. For example, one of the problems with using media queries is that essential desktop structure code ends up being hidden from legacy Internet Explorer. Respond.js is an excellent polyfill, but if you’re comfortable serving a single desktop layout to older IE, we don’t need JavaScript. We simply need to capture layout code outside of a media query and sandbox it under an IE only class name.

// set IE fallback layout to 8 columns
$ie-support = 8;

// inside of our breakpoint mixin (but outside the media query)
@if ($ie-support and $min <= $ie-support) {
	.lt-ie9 { @content; }
}

Perspective Regained

Thinking in columns means you are thinking about content layout. How big of a screen do you need for 12 columns? Who cares? Having Sass write media queries means you can use intuitive numbers for content layout. A fixed grid means more layout control and less edge cases to test than a fluid grid. Using CSS labels for activating responsive images means you don’t have to duplicate breakpoints across separations of concern.

It’s a harmonious blend of approaches that gives us something we need—responsive design that feels intuitive. And design that, from the very outset, focuses on what matters most. Just like our kindergarten teachers taught us: It’s what’s inside that counts.

About the author

Les James was born with the heart of an artist but the brain of a developer. Fortunately for him, front-end development is the perfect intersection between the art and science of crafting a website and he gets to pursue his passion for design at Capstrat by transforming ideas into code. Les is always thirsty for knowledge, so why don’t you drop some on him at @lesjames.

More articles by Les

Comments