Visitors to web applications nowadays are split nearly half and half between desktop and mobile. The experience of interacting with an application on these two kinds of devices is dramatically different. Is there a way to compensate using only CSS?
Consider a layout that is fixed and has multiple scrollable panes, like Airbnb's search results page. On desktop, it has a navigation pane across the top, a scrollable pane in the lower left, and a draggable map in the lower right. This clearly won't work on mobile due to space constraints. Additionally, the map would be difficult to use on mobile because dragging to pan would be confused with attempting to scroll the page.
In this exercise, we'll mock up a page to have a three pane layout like this, but on mobile, the lower right pane will disappear, the fixed navigation will become scrollable and the lower left pane will be inline below it.
At the top level of markup, there will be three elements, one for each pane in the layout. Each will be given an id attribute. Although a class would be sufficient for selecting these elements, using ids indicates that there is and will only ever be one element with the given presentation.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Responsive Layout</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav id="navbar">Navigation</nav>
<main id="primary-content">
<div>Main content</div>
<div class="filler"></div>
<div class="filler"></div>
<div class="filler"></div>
<div class="filler"></div>
<div class="filler"></div>
<div class="filler"></div>
<hr>
<footer>Footer content</footer>
</main>
<aside id="secondary-content">Optional content</aside>
</body>
</html>
Some filler elements are included in the main content that will take up space to test how well the page behaves when it is full of content.
In order to show the size and position of each of these elements, we'll add some placeholder styles to make the panes and filler visible.
body {
font-family: sans-serif;
/* body has a margin by default */
margin: 0;
}
.filler {
background: #ccc;
height: 15em;
margin: 1em 0;
}
#navbar {
background: #ddd;
padding: 1em;
}
#primary-content {
background: #eee;
padding: 1em;
}
#secondary-content {
background: #def;
padding: 1em;
}
In desktop view, it appears that this will work for mobile. Open the developer tools and switch to mobile emulation mode to test.
It would appear that this is not sufficient for mobile just yet. A key piece of markup is missing — the viewport meta tag.
Add the typical responsive meta tag to the head of the HTML document.
<meta name="viewport" content="width=device-width, initial-scale=1">
Now everything appears at the right scale for mobile. Time to get responsive.
It is fairly well accepted that styling for mobile first and then adding and modifying styles for larger viewports is the easier approach. At the present point in this design, mobile is practically finished. However, the optional content is still visible. Let's start by removing that using display: none
.
#secondary-content {
background: #def;
/* Hide secondary content on mobile */
display: none;
padding: 1em;
}
This should complete the layout on mobile. It looks good and is easy to scroll through. The next step is to switch back to desktop view by turning off mobile emulation mode or closing the developer tools and positioning the elements for sufficiently large screens.
The first big question to answer is, how big is "sufficiently large"? Perhaps it's the size of a tablet. iPads report their narrow side as 768 pixels. That will be the mobile "break point" or when we switch from mobile to desktop view.
With this decision made, we can make the optional content reappear when the device width is at least 768 pixels.
@media (min-width: 768px) {
#secondary-content {
display: block;
}
}
Next, we can apply absolute positioning to the panes inside of the media query block so that the positioning styles only apply on larger screens.
The navbar will be a fixed 50 pixels tall and span the width of the screen. The main and optional content will share the remaining space, splitting it at two thirds of the way across.
@media (min-width: 768px) {
#navbar {
/* This allows setting a specific height even though padding was applied */
box-sizing: border-box;
height: 50px;
left: 0;
position: absolute;
right: 0;
top: 0;
}
#primary-content {
bottom: 0;
left: 0;
position: absolute;
right: 33.333333%;
top: 50px;
}
#secondary-content {
bottom: 0;
display: block;
left: 66.666667%;
position: absolute;
right: 0;
top: 50px;
}
}
Almost there! There's a problem now because the main content is larger than its container. This makes it so that we can scroll the whole page, which is not the desired effect. To fix this, overflow: auto
can be added to the styles for #primary-content
in the media query section.
And that's it! This layout is now optimized for both desktop and mobile viewing.