Tuesday, April 09, 2013

Modular CSS with Media Queries and Sass

Modular CSS with Media Queries and Sass

Most developers nowadays are recognizing that if you're developing large applications that have different views and states, it is best to take a modular or object-oriented approach to your CSS development.

When you throw media queries into the mix, however, the you can lose some of the benefits of modularity — code that's easy to read, update, and maintain.

Let's look at different ways we can write our media queries, starting with what might be the most common approach — especially with those not yet using a preprocessor.

The Common Approach

To demonstrate what I think the majority of developers have done with media queries up to this point, here's a simple example:

.box-module {
	float: left;
	background: #ccc;
	margin-bottom: 1.2em;
}
  
.some-other-module {
	border: solid 4px #222;
	padding: 1.2em;
	margin-bottom: 1.2em;
}
  
/* ... other non-media query styles would go here ... */
  
@media screen and (max-width: 720px) {
	.box-module {
		float: none;
		clear: both;
		margin-bottom: .5em;
	}

	.some-other-module {
		border: solid 2px #222;
		padding: .8em;
		margin-bottom: .5em;
	}
}

@media screen and (max-width: 320px) {
	.box-module { margin-bottom: .2em; }
  
	.some-other-module {
		padding: .2em;
		margin-bottom: .2em;
	}
}

/* ... other media query styles would go here ... */

In the first two rule sets in this example, we have two separate elements on the page styled without media queries. (As a side point, those first two rule sets could be our mobile first styles, but it really doesn’t matter for the purpose of this theoretical example.)

After we declare those primary styles on our two elements, we then declare our media queries, repeating the same selectors, but with altered styles to suit the media features defined in the media queries.

The key part to take note of here is how this would look if we had thousands of lines of code. If that were the case, the media query styles that apply to the same modules would be significantly separated from the original styles for those same elements. Not ideal, especially when you're trying to keep related chunks of CSS together.

A More Modular Approach

The purpose of modularizing our CSS is to help it become much more flexible and scalable, but also to help make it easier to read and easier to maintain. As we can see from the previous example, that approach (where you throw all your media queries at the bottom of the stylesheet) can, to some extent, hinder maintainability.

Let's rewrite the example from above using a more modular approach:

.box-module {
	float: left;
	background: #ccc;
	margin-bottom: 1.2em;
}
  
@media screen and (max-width: 720px) {
	.box-module {
		float: none;
		clear: both;
		margin-bottom: .5em;
	}
}
  
@media screen and (max-width: 320px) {
  .box-module { margin-bottom: .2em; }  
}
  
.some-other-module {
	border: solid 4px #222;
	padding: 1.2em;
	margin-bottom: 1.2em;
}
  
@media screen and (max-width: 720px) {
	.some-other-module {
		border: solid 2px #222;
		padding: .8em;
		margin-bottom: .5em;
	}
}
  
@media screen and (max-width: 320px) {
	.some-other-module {
		padding: .2em;
		margin-bottom: .2em;
	}
}

This might seem a bit odd and counterproductive to those who are accustomed to putting their media queries at the bottom of their CSS file. This approach is discussed as an option in Jonathan Snook's book on modular CSS, where he discusses changing state with media queries.

Even before I started reading Jonathan's book, I've considered this approach, but pretty much immediately discarded the thought for probably the same reason that many of you are cringing at it right now: The fact that this clearly goes against the DRY (Don't Repeat Yourself) principle.

Pros and Cons

As we'll see in the following sections, if you're using Sass, the repetition becomes less of a factor and, from what I can tell, many developers are adopting this method.

But first, let's consider the pros and cons of this approach for pure CSS that's not using Sass.

Cons

  • Lots of repetition, which modular CSS discourages
  • More code, which could amount to hundreds of extra lines, thus larger files
  • Could be confusing to future maintainers, especially if not well documented
  • If you change a single media query state (e.g. "max-width: 720px"), you have to change it for all modules

Pros

  • Easier to read/understand/maintain for the original developer(s)
  • Could be much easier to read/understand for future maintainers, especially if well documented
  • Easier to write the initial code (more on this below)

Resolving Some of the Cons

To purists, the cons may seem like big problems. But I don’t think they're as big as they seem, even though they may appear to outweigh the pros.

First, I'm not concerned about the larger amount of code. If I'm minifying and gzip'ing, the difference in code quantity will be negligible.

Second, as suggested by the final "pro" point, if you're writing your media query for a module right next to the initial styles for the module itself, I believe this will make it significantly easier when debugging that particular part of the code. Since you have all the different styles for a particular module together — including all the different state changes via media queries — you'll quickly be able to make any changes and won't have to put off debugging the same module again at smaller breakpoints.

Modular Media Queries with Sass

When you factor in a preprocessor like Sass, this sort of thing becomes exponentially easier. If you're not familiar with some of Sass's media query-specific capabilities, it's worth checking out that part of their documentation, which talks about doing this exact thing.

So let's rewrite our example from above using Sass with nesting and variables:

$media: screen;
$feature: max-width;
$value: 720px;
$value2: 320px;
  
.box-module {
	float: left;
	background: #ccc;
	margin-bottom: 1.2em;

	@media #{$media} and ($feature: $value) {
		float: none;
		clear: both;
		margin-bottom: .5em;
	}
  
  @media #{$media} and ($feature: $value2) { margin-bottom: .2em; }
}
  
.some-other-module {
	border: solid 4px #222;
	padding: 1.2em;
	margin-bottom: 1.2em;
  
	@media #{$media} and ($feature: $value) {
		border: solid 2px #222;
		padding: .8em;
		margin-bottom: .5em;
	}

	@media #{$media} and ($feature: $value2) {
		padding: .2em;
		margin-bottom: .2em;
	}
}

Here, in addition to variables, we're using Sass's nesting capabilities, which don't require repeating the selector inside the media query block. This will compile to basically what we did in the Sass-less modular example above.

But we're not finished yet. Let’s make one final improvement to this code, using a fairly new Sass feature.

Modular Media Queries Using Sass with @content

We’re going to improve on this even more by using Sass's ability to pass content blocks to a mixin.

And I should point out that this is nothing new; the official Sass blog discussed this shortly before Sass 3.2 was released, and a few other blogs have discussed the benefits of using Sass to keep media query styles near the styles they override.

Here's our updated code using Sass's @content directive:

$media: screen;
$feature: max-width;
$value: 720px;
$value2: 320px;
  
@mixin modular-mq($breakpoint) {
	@if $breakpoint == medium {
		@media (#{$media} and $feature: $value) { @content; }
	}
	@else if $breakpoint == small {
		@media (#{$media} and $feature: $value2) { @content; }
	}
}
  
.box-module {
	float: left;
	background: #ccc;
	margin-bottom: 1.2em;

	@include modular-mq(medium) {
		float: none;
		clear: both;
		margin-bottom: .5em;
	}
  
	@include modular-mq(small) { margin-bottom: .2em; }
}

.some-other-module {
	border: solid 4px #222;
	padding: 1.2em;
	margin-bottom: 1.2em;

	@include modular-mq(medium) {
		border: solid 2px #222;
		padding: .8em;
		margin-bottom: .5em;
	}

	@include modular-mq(small) {
		padding: .2em;
		margin-bottom: .2em;
	}
}

The examples with the variables and whatnot are not necessarily going to be done like that. You can hardcode the values right into the mixin, or you could have some conditional logic that decides what the values will be. This is more or less a theoretical example.

Conclusion

Even with the two Sass examples, you're still, to some extent, repeating your media query breakpoints for every selector that needs an override. So I can understand if many developers don't like this approach.

Overall, I think the concept of keeping related styles near each other is much more in line with what we like to see in CSS and especially in modular or OOCSS. I always find it annoying when I have to sift through two different parts of a stylesheet to make a change to a single element on the page — a problem that has occurred much more often with the advent of media queries. I think this approach would resolve this type of thing.

I should also mention that there are some projects that aim to help in this area including Sassy Media, Susy, sass-mediaqueries and probably others that I haven’t yet seen. (Update: Scott Kellum tweeted to me about Breakpoint, which is worth checking out just for the Point Break movie homage).

via Impressive Webs by