A responsive experiment with CSS3 and jQuery: switching between accordion and tabs display

Written by Alexis Nominé on . Posted in Experiments

I've been playing lately with responsive design, trying to present data the best way depending on the screen size available. Most of the time, CSS3 media queries will be enough. However, some designs will also require a bit of jQuery. Is it possible to make it responsive as well? That's what I intented in this experiment.

For the purpose of this example, I have a list of 10 well known stars with a small bio. I want to display it as an accordion on smaller screens, and as vertical tabs on the bigger ones.

If you're too impatient to reed the whole article, have a look at the Demo

The markup

Here is what the markup looks like. Pretty simple, with the title of my page, the navigation for the tabs view, and the profiles:

<h1>Star Profiles</h1>
<nav>
 <ul>
  <li><a href="#bard-tipp">Bard Tipp</a></li>
  <li><a href="#neolardo-picadrio">Neolardo Picadrio</a></li>
  [...]
 </ul>
</nav>
<div id="profiles">
 <article id="bard-tipp">
  <h1>Bard Tipp</h1>
  <div class="description">
   <p>Fusce et justo purus, at vestibulum ligula. Sed nisi ante, volutpat vel sollicitudin vitae, pretium in tortor. Aliquam id massa nulla, in viverra risus. Morbi vestibulum sapien nisl, at consequat purus. Proin purus nunc, bibendum sit amet aliquam a, condimentum quis elit.</p>
  </div>
 </article>
 <article id="neolardo-picadrio">
  <h1>Neolardo Picadrio</h1>
  <div class="description">
   <p>Fusce et justo purus, at vestibulum ligula. Sed nisi ante, volutpat vel sollicitudin vitae, pretium in tortor. Aliquam id massa nulla, in viverra risus. Morbi vestibulum sapien nisl, at consequat purus. Proin purus nunc, bibendum sit amet aliquam a, condimentum quis elit.</p>
  </div>
 </article>
 [...]
</div>

The style

I'll design for mobile first, so let's add a bit of styling for the accordion:

body{
	margin: 0;
	padding: 0;
}

/* the accordion handles */
#profiles h1{
	border-bottom: 1px solid #999;
	background-color: #ddd;
	margin: 0;
	padding: 0 1em;
	font-size: 1em;
	line-height: 2;
	font-weight: normal;
	cursor: pointer; /* display a hand cursor, like links */
}
#profiles :first-child h1{
	border-top: 1px solid #999;
}

/* visually indicate when it's open */
#profiles .active h1{
	background-color: #f5f5f5;
	border-bottom: 0;
}

/* the panels, closed by default */
#profiles .description{
	padding: 0 1em;
	display:none;
}

/* needed only for the tabs, so we hide it here */
nav{
	display:none;
}

For the tabs, we'll put the style in a CSS3 media query block (@media screen and (min-width:760px)), applied only when the viewport is wide enough:


@media screen and (min-width:760px){
	body{
		max-width: 980px;
		margin: 0 auto;
	}
	
	/* display the tabs handles on the left side */
	nav{
		display: block;
		width: 12.5em;
		float: left;
		margin-top: 1em;
	}
	nav ul{
		list-style: none;
		margin: O;
		padding: 0;
	}
	nav a{
		display: block;
		color: inherit;
		text-decoration: none;
	}
	nav li{
		border-bottom: 1px solid #999;
		border-left: 1px solid #999;
		background-color: #dcdcdc;
		margin: 0;
		padding: 0 1em;
		font-size: 1em;
		line-height: 2;
	}
	nav li:first-child{
		border-top: 1px solid #999;
	}
	/* visually indicate when it's open */
	nav .active{
		background: white;
		margin-right: -1px;
		font-weight: bold;
	}
	
	#profiles{
		margin-left: 12.5em; /* make room for the handles */
		border: 1px solid #989898;
	}
	
	/* hide all the profiles except the one that's active */
	#profiles article{
		display: none;
	}
	#profiles .active{
		display: block;
	}
	
	/* override accordion styling */
	#profiles h1{
		background-color: transparent;
		border: none;
		cursor: auto;
	}
	#profiles h1:first-child{
		border-top: 0;
	}
	#profiles .active h1{
		background-color: transparent;
	}
	#profiles .description{
		display: block;
	}
}

The script

The key to make the jQuery script responsive is to use the event $(window).resize() and the property $(window).width():

var panels = $('#profiles article'); // cache the profiles
var navlinks = $('nav li'); // cache the nav links
var view = ""; // store the actual view
var minWidth = 760; // breakpoint between views

function accordionHandler() {
	// get the target panel
	var panel = $(this).parent();
	// if not already open, open it. else, close it
	panel.toggleClass('active').children('.description').slideToggle();
	// close every other panels
	panels.not(panel).removeClass('active').children('.description').slideUp();
	// select corresponding navlink in case view switches to tabs later
	var navlink = navlinks.has('a[href=#'+panel.attr('id')+']');
	navlink.toggleClass('active');
	navlinks.not(navlink).removeClass('active');
}

function tabsHandler(e) {
	// prevent the default click event
	e.preventDefault();
	// get the target panel
	panel = $($(this).attr('href'));
	// open it
	panel.addClass('active');
	// close every other panels
	panels.not(panel).removeClass('active');
	// select corresponding navlink
	navlinks.removeClass('active');
	$(this).parent().addClass('active');
}

function switchView() {
	// if the viewport is wider than minWidth
	if ($(window).width() > minWidth) {
		// switch to tabs view if it's not already the case
		if (view != "tabs") {
			view = "tabs";
			// remove accordion behavior
			panels.children('h1').off('click',accordionHandler);
			panels.children('.description').show();
			// add tabs behavior
			navlinks.children('a').on('click',tabsHandler);
			// open the first tab if no tab is open
			if (!panels.is('.active')){
				navlinks.first().addClass('active');
				panels.first().addClass('active');
			}
		}
	// if the viewport is smaller than minWidth
	} else {
		// switch to accordion view if it's not already the case
		if (view != "accordion") {
			view = "accordion";
			// remove tabs behavior
			navlinks.children('a').off('click',tabsHandler);
			// add accordion behavior
			panels.children('h1').on('click',accordionHandler);
			// hide every panel except if there's one "active"
			panels.filter('.active').children('.description').show();
			panels.not('.active').children('.description').hide();
		}
	}
}

// load the right view at init and when the viewport changes
$(document).ready(switchView);
$(window).resize(switchView);

Last thing, we'll need to add this small meta in the document head, so that smartphones know that the page is made for them, and won't try to adapt it:

<meta name="viewport" content="width=device-width, initial-scale=1" />

If we want to take care of the poor IE8 users, we'll need to add two polyfills: one to understand HTML5, the other to understand CSS3 min/max-width media queries (available here:

<!--[if lt IE 9]>
 <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
 <script src="respond.min.js"></script>
<![endif]-->

That's it! We now have a responsive accordion/tabs display.
What do you think? Anything you'd make differently?

Tags: , ,

Trackback from your site.

Leave a comment