Responsive Web Design: A Step-By-Step Guide

Responsive Web Design: A Step-By-Step Guide

Tips and tricks for implementing a responsive web interface

Responsive web design—the art of creating a single website with a layout and features that adapt to the screen size of different browsing devices—is becoming more important as end users view web pages on mobile phones, tablets, TVs, and so on. And yes, you've still got users on older browsers such as Internet Explorer (IE) 6.0, 7.0, and 8.0 to consider.

How can a developer keep these disparate users happy? I show you in this article. I take you step-by-step through a responsive web interface, providing a host of tips and tricks for successful responsive web design. Note that many of the tips and tricks apply only to browsers that support CSS3 media queries and the viewport tag. Supporting browsers include Opera Mobile and the default iOS and Google Android browsers.

Planning for Multiple Layouts

Before you jump into a code editor, you should think about how many layouts you need for your website. Are you building a simple, single-column site, in which case you probably don't need to do much except adjust the column to the size of a narrow mobile screen? Or are you dealing with a much more complex website with lots of text and images, in which case you would want to consider multiple layouts?

Because there is a lack of good prototyping tools available for responsive web design, I find it works best to sketch ideas for the different layouts on paper. To demonstrate the concepts in this article, I built a web page for Goatboy, a fictional farm supplier (Figure 1).

Figure 1: Example web page with wide-screen layout set at 1200 pixels
Figure 1: Example web page with wide-screen layout set at 1200 pixels

After you sketch your layouts, create just one or two graphic mock-ups with a solid set of branding guidelines and test regularly during development to make sure the look and feel of various web pages don't stray far from the guidelines. Next, create a rough prototype using HTML and Cascading Style Sheets (CSS) and incorporating media queries so you can see how the content will work on different screen widths. You can fit the final content later, but for now a rough prototype is good enough to work.

Note that retrofitting a complex legacy site with a responsive layout would probably be difficult and cost significantly more than creating a completely new website from scratch. You should consider this when working with existing sites, especially if they are large enterprise beasts.

Identifying Elements That Need to Change

Generally, there are two ways to approach the "what parts of my layout need to change?" conundrum:

  • Content breakpoints use media queries to change a layout at the point where it would start to break (e.g., where an h1 heading would break onto two lines, or where content columns become too narrow). This approach tends to take a bit more thought and not be as precisely controlled as the other option (device breakpoints, explained below). But content breakpoints are more defensive in terms of supporting unknown devices.
  • Device breakpoints use media queries to provide specific layouts for target devices (e.g., the iPhone or iPad) or whatever else your client wants specific support for. This allows more precise control, but it is less flexible.

In reality you will probably use a combination of both approaches, perhaps with media queries that kick in when screen dimensions get smaller than 1024 pixels to provide a great layout for the iPad and other same-sized tablets, at 768 pixels to provide for iPad portrait width, and at 480 pixels for iPhones, etc. Then you could have a media query that looks OK on smaller devices (e.g., less than 400 pixels) and one that looks good on larger displays (e.g., wide screens of 1300 pixels or more).

Implementing Basic Layouts: Mobile First

Time to move beyond planning and look at some code. When media queries were first employed, developers set the default layout in the first block of CSS encountered by the browser and then inserted media queries below to provide for smaller screen sizes. For example:

 /* Default styles for "standard" desktop or landscape tablet layout, probably about 1024px wide?  */

@media all and (max-width: 800px) {
  /* Styles for a narrow desktop or portrait tablet */
}

@media all and (max-width: 600px) {
  /* Stylistic tweaks to fix the above layout when it gets down to narrower climes */
}

@media all and (max-width: 480px) {
  /* Styles for "average" mobile phone landscape; e.g., an iPhone  */
}

@media all and (max-width: 320px) {
  /* Styles for iPhone portrait and other really narrow layouts  */
}

@media all and (min-width: 1300px) {
  /* Optional really wide layout for wide-screen monitors  */
} 

However, more often than not these days you see media queries done the opposite way, with mobile layouts set as the default and all other layouts specified afterwards in progressively wider media queries. This is called "mobile first" and has many advantages. Some argue that it's easier to begin with a mobile layout and expand on it rather than start with a desktop layout and figure out how to cram content onto a narrower screen. Plus, the mobile first concept has advantages for responsive images, as I discuss later.

Take a look at the different layouts I created (you can also check out the example), pausing to consider the markup structure:

I only listed the major elements. To see the full markup look at the example HTML, and to check out the full code look at the CSS.

The default mobile/narrow layout. The mobile layout is created by the CSS code at the top of the file, before any media queries.

There is no room for multiple columns on the narrow layout, so it's a single column. I set containers to 100 percent width with a maximum width of 480 pixels and centered with the width: 0 auto margin trick (except for the footer, which is always at 100 percent). This way, when the layout reaches 480 pixels wide, it doesn't stretch but stays in the center of the larger space. This works well for smaller devices while maintaining readability and content lines that are not too long on larger devices. I set video at maximum width: 100 percent of content containers; so videos expand nicely and don't break the layout at smaller widths.

Also note that at really small widths the navigation menu is a select element menu, whereas at larger widths it's a series of navigation buttons. This is a good technique to use for small-screen mobiles, especially if your menu is a vertical navigation bar. On really small screens vertical navigation bars take up far too much real estate, so it's a good idea to swap them out for a space-saving menu like the one I use in Figure 2, and/or move the menu to the bottom of your pages using JavaScript or the legendary CSS table-caption technique.

In the example I simply include both menus in the markup and hide the unordered list that forms the basis of the buttons using display: none by default. Then at 500 pixels the first media query swaps the display: none with the select element menu and performs a few other small layout tweaks as well.

Figure 2: Default, single-column layout for mobile devices
Figure 2: Default, single-column layout for mobile devices

The middle layout.  I decided that the narrow, one-column layout started to look a bit silly at about 700 pixels, so at that point I changed to a two-column layout, making the navigation and header longer to fit in the space (Figure 3).

Figure 3: Two-column layout for devices with screens larger than 700 pixels
Figure 3: Two-column layout for devices with screens larger than 700 pixels

The two-column nature of the HTML looks like this:

section {
  max-width: none;
  width: 44%;
  margin: 0;
}

.special {
  float: left;
}

.feature {
  float: right;
  margin-right: 5%;
}

footer {
  clear: both;
} 

I simply made the header and nav elements wider, like this:

nav {
  max-width: none;
  width: 80%;
}

nav {
  padding-top: 2rem;
}

header {
  max-width: none;
  width: 100%;
  background: url(../images/goat.png) 100% top no-repeat, url(../images/bales.png) center -140px no-repeat;
} 

Note that I replace the bale.png background image in Figure 2 with a much wider background image—bales.png—for this width (Figure 3). This helps optimize the end-user experience. Narrow screens that don't need the wider image will never have to download it and waste bandwidth.

There are a few other tweaks in this layout, but that's all the exciting stuff.

The "average" desktop layout. At 1000 pixels the third media query comes into play, changing the navigation menu into a vertical column beside the two content columns to create a three-column layout (Figure 4).

Figure 4: The third media query kicks in at 1000 pixels to create a three-column layout
Figure 4: The third media query kicks in at 1000 pixels to create a three-column layout

This is handled in the HTML like so:

nav {
  float: left;
  width: 20%;
  margin-top: 0.75rem;
}

nav li {
  display: block;
  margin-bottom: 2rem;
}

nav li a {
  width: 80%;
  font-size: 3.6rem;
  line-height: 3.5rem;
} 

This looks like a lot, but basically I set the list items to their default display: block (I had them set to display: inline earlier so that they'd align in a horizontal row—Figure 3), and adjust widths, line-heights, margins, and padding so that the navigation and other content fits nicely inside their new space.

The wide-screen layout. The wide-screen layout (Figure 1) is really a bit of a cheat. All I do is use a media query to set the content to a fixed width of 1200 pixels when the viewport width reaches 1200 pixels and use the margin: 0 auto trick so that the layout doesn't get any wider, keeping it centered on the web page:

body {
  width: 1200px;
  margin: 0 auto;
} 

This works well for wide-screen displays—you don't want the layout to get any wider than 1200 pixels, otherwise it will look fragmented and silly.

Using Viewport for Proper Behavior on Mobile Devices

The techniques I've shown so far provide a nice range of layouts optimized for browser viewports of different widths. I have not yet provided all the puzzle pieces you need to build optimized layouts for mobile browsers—if you look at the example on a mobile device, you don't see the customized narrow-screen layout; you see the desktop layout shrunk down (Figure 5).

Figure 5: Two-column layout in Figure 3 shrinks to fit on mobile devices
Figure 5: Two-column layout in Figure 3 shrinks to fit on mobile devices

This isn't good for anyone—but why?

The reason is that mobile devices lie! Rather than render sites at their physical width, mobile devices render websites at an assumed wider size (e.g., Opera Mobile uses 980 pixels) and then shrink the result. This is a good defense mechanism, considering that many sites aren't built to be responsive (the average liquid desktop layout would look terrible rendered at 380 pixels wide, and fixed-width layouts would be too big to fit on screen), but it doesn't help us and our media queries.

Fortunately there is a way to override this behavior: the proprietary viewport meta tag, which was invented by Apple to better control how applications are displayed on the iPhone and then adopted by other browser vendors because it is a jolly good idea. To make the example render at the real physical pixel width of the device, add the following to the head element in the HTML document:

View the second version of the example to see the result (Figure 6).

Figure 6: The viewport meta tag lets you control how web pages are rendered on mobile devices
Figure 6: The viewport meta tag lets you control how web pages are rendered on mobile devices

I can say a lot more about viewport, but that is, unfortunately, beyond the scope of this article. To find out more, read Andreas Bovens' "An Introduction to Meta Viewport and @viewport."

Supporting Older Browsers

Another problem you'll have to think about sooner or later is older browsers that don't support media queries and other such modern technologies, but still have significant enough market share to support. When I say this, I'm generally talking about IE 6.0, 7.0, and 8.0.

Because they don't support media queries, loading my example in one of these browsers will give you the default mobile layout. Effectively, older browsers ignore media queries, which is no good at all! Fortunately there are a couple of techniques to make things rosy again.

Polyfilling. Polyfilling usually employs JavaScript to "fake" support for open-standard features not natively supported by older browsers. In the case of media queries, there are two polyfills to choose from:

  • respond.js has a very small footprint and works great when you only need to polyfill width/height media queries.
  • css3mediaqueries.js is a bit bigger than respond.js but supports more media queries.

Using either is as simple as including the JavaScript file on your page using a script tag.

Fixed-width fallbacks. A conditional comment in the HTML can reference an IE-only stylesheet that sets the web pages' main content at a fixed width when rendered in old versions of IE:

 

I've often seen liquid layouts do funny things in IE, so having a single-width layout for older versions makes things easier. Plus, it can solve potential conflicts with video support. Because IE doesn't support the HTML5 video tag, you must use Flash in IE; and because Flash video doesn't respond well to tricks like max-width: 100 percent to make a video stay inside its column (I explain this technique above), and old versions of IE don't support max-width anyway, the best bet is to eliminate such problems altogether by using a fixed-width layout for older IE browsers.

You'll probably need other fixes in your IE-only stylesheet, so include the polyfill code there, too, so that newer browsers don't download unnecessary code!

For this article I use respond.js to provide support for my media queries in IE 6.0, 7.0, and 8.0, and I put a few fixes in the iefixes.css stylesheet to make the page at least readable in those versions of IE. You can see these techniques in the example.

You also can use Modernizr to determine whether browsers support specific web features, and then write JavaScript and CSS conditions to create different browser experiences as appropriate.

Incorporating Responsive Media

So far I've covered layouts, which go a long way toward getting us where we want, but they are really just the tip of the iceberg for responsive design. Another element to consider is responsive images. Narrow-screen devices not only need smaller images, but these devices are often on slower networks than desktops and therefore perform better downloading smaller files.

CSS background images. When you use the mobile first technique, background images are easy to handle (as I describe above). All browsers will load the narrow-screen image by default, and then wide-screen devices will interpret the media queries and replace the small image with a larger one, provided of course that the browser on the wide-screen device supports media queries.

If you don't use mobile first, then the mobile device would load the large image first, followed by the small image as well when the media queries kick in. That obviously doesn't help anyone in terms of bandwidth savings.

HTML video with Flash fallback. The code for the HTML5 video I embedded on the Goatboy web page ("Stress Testing Our Hay") contains two source elements that point to different video files, as well as a set of object/embed elements:

 

One source element points to an MP4 version of the video played by modern IE and Safari browsers, and the other points to a WebM version played by Opera, Firefox, and Chrome browsers. The type attribute isn't mandatory, but it's a good idea to include it within all source elements because it tells the browser what type of video is in each source so the browser can pick the one it needs straight away. If you don't include the type attribute, the browsers will download bits of each video in an effort to determine their formats, which, again, is a waste of time and effort.

The object element and its contents embed a Flash video on the page for older browsers that don't support HTML5 video. I don't discuss this in detail, but you can find a more detailed guide on dev.opera.com: "Simple HTML5 Video Player with Flash Fallback and Custom Controls."

Because the video on Goatboy generally stays the same size regardless of the device accessing the website, I don't provide multiple sizes to accommodate specific devices. But if you're working on a responsive design in which the video is likely to change size and want to provide a smaller video for narrow-screen devices, use the media attribute within the source element:




 

The browser loads the video that is both in a format it understands and has a media attribute that matches the device's viewport width. That way, narrow screens access the smaller video file and wider screens access the bigger one.

Note that the native WebKit browser for Android devices is picky about the type of video it plays, a long-standing bug that Google has yet to fix. To avoid problems with MP4 videos playing on Android devices, encode these videos as described in "Encoding Video for Android" by Peter Gasston.

HTML images. Content images, embedded in a web page using the img tag, are much more difficult to make responsive, and the subject is beyond the scope of this article (I could easily write a whole article on the topic). There are a number of native solutions in the works for making HTML images responsive (read "Responsive Images: What's the Problem, and How Do We Fix It?" by Matt Wilcox for an in-depth discussion), but none of them are cross-browser reliable. The best one is Matt's Adaptive Images technique, which relies on PHP and custom .htaccess values. It feels a bit odd to rely on a server-side solution for what is essentially a client-side problem, but this is the best solution right now. Hopefully open standards will improve in the not too distant future so that web developers won't have to rely on such inefficient methods.

Adjusting for High-Resolution Devices

As if dealing with the wealth of differing devices isn't difficult enough, we're also seeing the emergence of high-resolution phones, laptops, and tablets. These devices apply a default zoom to web content because if they didn't, everything would appear small and unreadable. This isn't much of a problem for vector content such as text, scalable vector graphics (SVG), and CSS-generated drop shadows and the like, but it gives raster images a hard time. When high-resolution devices zoom in on raster images, the images often look blocky and pixelated. In this section I provide tips for mitigating such problems.

Use as few images as possible. Simple but effective: You can cut down on the problems with images by using as few as possible. Use SVG or CSS3 features instead to create graphical effects such as gradients, drop shadows, and so on. The downside of this is that these effects don't work in old browsers such as IE 6.0, 7.0, and 8.0, but you can't keep from using modern web technologies just to satisfy the ever-dwindling number of users stuck on those browsers. My advice is to rely on graceful degradation and progressive enhancement as much as possible: Test your code in these browsers and make sure their users get an experience that is accessible and usable, even if it doesn't look as pretty.

If you really need your code to work the same in older browsers as it does in current ones, you can:

  • Use polyfills to fake support for older browsers; for example CSS3PIE is a library that fakes support for things such as gradients and rounded corners.
  • Not use CSS3 graphic generation features.

Serve larger images and constrain their size. To stop an image from looking pixelated on high-resolution devices, create a larger image than is necessary and constrain it using HTML width/height attributes. For example:

  

On high-resolution devices there is enough graphical information inside the image that it will still look sharp even when zoomed to double its size.

Serve larger images using resolution media queries. There is a proprietary media query feature called min-device-pixel-ratio that lets you serve different CSS content to devices with different resolutions. In my example I include the following media query at the bottom of the CSS file to serve bigger background graphics to high-resolution devices:

nav {
  background-image: url(small-header-image.png);
}

@media all and (-webkit-min-device-pixel-ratio: 1.5) {
  nav {
    background-image: url(large-header-image.png);
  }
}

@media all and (-o-min-device-pixel-ratio: 3/2) {
  nav {
    background-image: url(large-header-image.png);
  }
} 

Note that this media query is supported by the use of vendor prefixes, and Opera and WebKit browsers require different unit syntax. Annoying, but it will go away soon as browsers are adopting an official media query specification for handling resolution, which, funnily enough, looks like:

@media all and (min-resolution: 1.5dppx) {
  nav {
    background-image: url(large-header-image.png);
  }
} 

The unit in the resolution media query is dots per pixel; in other words, the number of physical device pixels that make up each CSS pixel. Yes, pixels are a relative unit, which can be a bit difficult to wrap your brain around.

Using Web Fonts in Place of Icons

One last technique I'll mention that helps cut down on HTTP requests and bandwidth and provides images that scale well on high-resolution devices is using web fonts for icons instead of images.

In my code, I imported a webdings-style font to use for my navigation menu icons. Here's how you do it: First, find an icon/webdings font that contains the icons you want to use. Next, import the font into your page using CSS3 web fonts (see the top of the CSS in example 1 for the import blocks).

To cut down on code repetition, I also use a nifty little feature in HTML5 called data attributes. Basically, you can contain metadata within your elements by placing it inside the form data-*. Inside each data attribute I include the character that corresponds to the icon I want to show in that navigation button. So in my navigation menu I include data-icon attributes:

  • Home
  • Tools
  • Materials
  • Livestock
  • Then, to insert the icon in each case, I use generated content's attr() to insert the contents of the data-icon attribute:

    nav li a:before {
      content:attr(data-icon);
      font-family: lcr_on_the_farmregular;
      font-size: 2.5rem;
      color: white;
      text-shadow: 1px 1px 3px black;
      position: relative;
      right: 4px;
      bottom: 2px;
    } 
    
    

    Note that I also position and style the icons further using color, text-shadow, position, and so on. This is a nifty little trick, but beware that it won't work on older versions of IE.

    For Further Reading

    And there you have it: a guide to solving the most common problems you'll encounter when implementing a responsive web interface with HTML5 and CSS3. Read more of my tutorials on dev.opera.com, and check out my book, Practical CSS3: Develop and Design, for more practical CSS3 techniques. Now that you've learned how to develop a web application, the next series of articles will delve into more advanced topics starting with learning how to render data on the client with jQuery Templates.

    Hide comments

    Comments

    • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

    Plain text

    • No HTML tags allowed.
    • Web page addresses and e-mail addresses turn into links automatically.
    • Lines and paragraphs break automatically.
    Publish