Pure CSS3 tab widget with :target

In recent years it has become fashionable to take content on a page and put it into a tabbed widget for display. This is usually done using JavaScript. There are a number of plug-ins for jQuery that do this, and most JavaScript widget libraries have something similar.

The other day I was browsing around over at QuirksMode and a stumbled upon a new CSS3 selector I had never noticed before. It got me thinking and I came up with a pure CSS tabbed widget using it.

We will start with some relatively semantic HTML defining a series of content blocks and links to them.


<ul id="nav">
    <li><a href="#part1">Part 1</a></li>
    <li><a href="#part2">Part 2</a></li>
    <li><a href="#part3">Part 3</a></li>
</ul>
<div class="tabs">
    <div id="part1">Part One</div>
    <div id="part2">Part Two</div>
    <div id="part3">Part Three</div>
</div>

And a little initial styling:


.tabs > div {
    width: 300px;
    margin: 0 1em 1em 1em;
}
#nav {
    padding: 0;
    margin: 1em 1em 0 1em;
}
/* make the items layout side by side without bullet points */
#nav li {
    display: inline;
    margin: 0 3px 0 3px;
    list-style: none;
}
#nav a {
    text-decoration: none;
    color: #000;
}

Now comes the CSS that does the magic:


/* Hide all tabs that are not the current target */
.tabs > div:not(:target) {
    display: none;
}
/* Show the targeted tab */
.tabs > div:target {
    display: block;
}

So what is happening here? The new CSS3 :target pseudo-class applies it's rules to any element that is targeted by a url hash. The first rule uses the negation pseudo-class, :not, to hide any tabs that are not currently targeted. The second rule uses :target to display any tab that is currently being targeted. Since we used :not to hide our tabs instead of a blanket statement like .tabs > div, browsers that do not understand :target will also ignore the code that hides the tabs. Thus gracefully degrading, leaving all of the content visible. At present Internet Explorer is the only major browser that does not support :target, but that will soon change with the release of IE9. If we sprinkle a little extra CSS into the mix we can give each tab and it's content a unified look by giving each unit it's own background color:


/* make each link/content pair the same color to make them look like one unit */
#nav li:nth-child(1) > a , .tabs > div:nth-child(1) {
    background: #6ff;
}
#nav li:nth-child(2) > a, .tabs > div:nth-child(2) {
    background: #f6f;
}
#nav li:nth-child(3) > a, .tabs > div:nth-child(3) {
    background: #6d6;
}

This technique does not allow the fancy animations that JavaScript widgets do, but it is incredibly simple, all the work is being done by two CSS declarations. With a little more CSS it shouldn't be to hard to refactor this to use CSS transitions, alleviating objections about it's plainness. The one real draw-back that I can see to this technique is that initially none of the tabs are displayed unless you come to the page from a link that includes a hash tag pointing to one of the tabs. The final product can be seen here.

    Archived WordPress Comments

  • AhmedMarch 30, 2011 09:11 am
    Nice! Really simple. And It works! But i have a question. How to show "Part 1" by default? I mean, it showing when someone open my page.
  • PeterApril 3, 2011 10:57 pm
    As mentioned in the last paragraph, you'd have to make sure all links that point to the page contain the hash for a tab (page.html#part1, etc). Outside sites could of course break this if they link to your page without a hash. If you don't mind sprinkling in a little JavaScript you could put something like:
    if  (document.location.hash === '') {
       document.location.hash = 'part1';
    }
    somewhere in the page to force it for the vast majority of users who will have JavaScript turned on.
  • ADALDecember 18, 2012 06:31 am
    Very good documented! Thanks! it's the best idea for a web page.

Previous: Facebook's XHP adds XML to PHP's syntax

Next: UCSV 1.0.2 released