Grow your CSS skills. Land your dream job.

CSS3-Only Tabbed Area

Published by Chris Coyier

MORE CURRENT ARTICLE: Functional CSS Tabs Revisited

When you think of "tabs", your mind might go right to JavaScript. Watch for a click on a tab, hide all the panels, show the one corresponding to tab just clicked on. All major JavaScript libraries tackle tabs in some way. But there is a way to accomplish this same idea with "pure CSS". Just as we did with the CSS Image Switcher, let's tackle this traditionally JavaScript project with only CSS.


Tabs: a ubiquitous design pattern

:target pseudo class selector

The major empowering concept here is the CSS pseudo selector :target. :target is used in conjunction with ID selectors. The selector will match when the current URL contains a hash-tag of that exact ID. So if the current URL is:

http://css-tricks.com/#big-bam-boom

And there was an element with that ID on the page:

<h1 id="big-bam-boom">Kaplow!</h1>

Then this selector will match:

#big-bam-boom:target { color: red; }

How does one get to a URL with such a hash tag? You have links that activate them:

<a href="#big-bam-boom">Mission Control, we're a little parched up here.</a>

Browser Support / CSS3

Normally I might end a tutorial like this with a little section on browser support. But it's rather important in this case so let's get it out of the way now. :target is considered CSS3. It has wide support across all the major current browsers. Of course I'm all abut dropping support for IE 6, so who cares if it doesn't work in that (it doesn't), but :target is also not supported in IE 7 or 8. These browsers are still very much on the radar, which puts this whole tutorial in a fun/educational category rather than a use-this-in-live-production category.

Of course, if you wanted to use it in production, one option would be to use conditional comments to add JavaScript to make them to work. We won't specifically cover that here.

Clean HTML

Let's start this out right with some nice and clean HTML markup for our soon-to-be tabbed area:

<div class="tabbed-area">

	<ul class="tabs group">
	    <li><a href="#box-one">Tab 1</a></li>
	    <li><a href="#box-two">Tab 2</a></li>
	    <li><a href="#box-three">Tab 3</a></li>
	</ul>
	
	<div class="box-wrap">
	
		<div id="box-one">
		  <!-- box two content -->
		</div>
		
		<div id="box-two">
		  <!-- box two content -->
		</div>
		
		<div id="box-three">
		  <!-- box two content -->
		</div>
	
	</div>

</div>

I'd call that perfectly clean. Even with CSS disabled, you would see a list of links each of which would jump down the page to the div with the content related to that link.

CSS

The tabs themselves we'll set up as a horizontal row of links.

.tabs { list-style: none; }
.tabs li { display: inline; }
.tabs li a { color: black; float: left; display: block; padding: 4px 10px; margin-left: -1px; position: relative; left: 1px; background: white; text-decoration: none; }
.tabs li a:hover { background: #ccc; }

When you float all the links like that, the parent will collapse, so let's chuck in the old clearfix class so we can use it on the parent ul so it has a natural height. No need for the IE 6 and 7 hacks here, since neither of those support this technique anyway.

.group:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; }

Now let's set up the very basic styling for the panels. There is a wrapping div for all the panels. The purpose of that is to set a relative positioning context so we can absolutely position panels inside of it. All the panels will be of equal height and width and positioned exactly on top of each other. Both panels and tabs share the same 1px border.

.box-wrap { position: relative; min-height: 250px; }
.tabbed-area div div { background: white; padding: 20px; min-height: 250px; position: absolute; top: -1px; left: 0; width: 100%; }
.tabbed-area div div, .tabs li a { border: 1px solid #ccc; }

Now the magical part that makes it work is as simple as adjusting the z-index of the panels when they are "targeted".

#box-one:target, #box-two:target, #box-three:target {
  z-index: 1;
}

Ruh-Roh... What about current tab highlighting?

What we have so far is a totally functional tabbed area. You click the tab, the corresponding content in that tab loads. Functional, but not the most helpful UI. There is no feedback at all which tab is the currently showing tab, either when the page loads or even when you click to view a different tab.

This is a fairly major hurdle. I find a way to solve it, and we'll go through that here, but it's dirty. The root of the issue is that we can't select back "up" the element tree. So if we need the panels to have ID's, the only thing we can affect in CSS is decedents of that div when it is in :target. For example:

#box-four:target a[href="#box-four"] { color: red; }

That would be a cool way to select only that particular link when that panel is active, but as of now, we can't do that because that link isn't a descendant of the panel.

The only way I've been able to solve this is to actually just make the navigation descendants of the panels. This is a bummer, because that means that each panel needs to repeat the tabs....

<div class="box-wrap">

	<div id="box-four">
        <!-- content for panel -->
      
        <ul class="tabs group">
            <li class="cur"><a href="#box-four">Tab 4</a></li>
            <li><a href="#box-five">Tab 5</a></li>
            <li><a href="#box-six">Tab 6</a></li>
        </ul>
	</div>
	
	<div id="box-five">
	    <!-- content for panel -->
	
        <ul class="tabs group">
            <li><a href="#box-four">Tab 4</a></li>
            <li class="cur"><a href="#box-five">Tab 5</a></li>
            <li><a href="#box-six">Tab 6</a></li>
        </ul>
	</div>
	
	<div id="box-six">
	    <!-- content for panel -->
	
        <ul class="tabs group">
            <li><a href="#box-four">Tab 4</a></li>
            <li><a href="#box-five">Tab 5</a></li>
            <li class="cur"><a href="#box-six">Tab 6</a></li>
        </ul>
	</div>

</div>

Very much not ideal, I know. But now that the lists are inside the panels, we can just use a "current" class on the list item that is the correct corresponding link and style that. And we'll make sure the current panels tab navigation is positioned above the panels and is "on top" when it's panel is.

.cur-nav-fix .tabs { position: absolute; bottom: 100%; left: -1px; }
.cur-nav-fix .tabs li a { 
   background: -webkit-linear-gradient(top, white, #eee); 
   background: -moz-linear-gradient(top, white, #eee); 
   background: -ms-linear-gradient(top, white, #eee); 
   background: -o-linear-gradient(top, white, #eee); 
}
#box-four { z-index: 1; }
#box-four:target .tabs, #box-five:target .tabs, #box-six:target .tabs { z-index: 3; }
.cur-nav-fix .tabs li.cur a { border-bottom: 1px solid white; background: white; }

Now with current tab highlighting!

Update

The above code, as mentioned, is definitely not a good way to go. I played with this whole idea a bunch more and the demo linked to now below has a whole bunch of different ideas for this including some decent solutions.

Demo and Download

Use at will, just be aware of the IE 7 problem. All of the HTML and CSS are right on one page, so if you want to "download" it, just copy and paste the code into an html file and save it.

View Demo

Hash it out

You'll notice in the demo that if you click a tab in the one on the left and then click on a tab in the one on the right, the area on the left will revert back to it's default slide rather than keep it's current slide. That all goes back to :target and how it's related to the hash in the URL. There is really no way around this without bringing in JavaScript, so if that's not gonna work for you, you should probably just go JavaScript from the get-go.

Also, hash tag links "jump down the page" when clicked, so also note that that when you click a tab your browser window will pop down to have that tab be the top-most element (if there is enough room to scroll down on the page). Again, no fighting that without JavaScript to my knowledge.

Other resources

Comments

  1. sliver37
    Permalink to comment#

    haha awesome article with your signature humour thrown in!

    Once again IE swoop

    • sliver37
      Permalink to comment#

      Once again IE swoops in to make sure we can’t use this for clients*

      Don’t ask how I missed typed that.

  2. Michał Czernow
    Permalink to comment#

    Fun article, but I’m going to be anal. Isn’t this mixing up layout with behavior? It’s clever technic; unfortunately – not supported by CSS purpose and mechanism. That’s why it has some shortcomings, pointed out in the article.

  3. For a fun/educational category demonstration – well played.

  4. I don’t think this is a good article. Just implement how the CSS3 works. Even the solution (current tab highlighting) is so dirty. I think this won’t be apply in any web app.

  5. noahgelman
    Permalink to comment#

    I have a concern about this. With multi-tabbed content like this, the user would likely be going back and forth from tab to tab. But now if they want to hit ‘Back’ to get to the previous page before the one with the tabbed content, it just sends them back to the last tab selected so probably not best for main content.

  6. Interesting article. I would never ever use it for anything, but it’s interesting to see how flexible CSS3 can be, even for things it wasn’t intended to be used for. So for the naysayers, no it’s not for production use, but just an interesting trick using css.

    • It’s nice to use :target for highlighting content; like in those long FAQ lists when you jump down the page for the answer. You can give the target area a different background color to help guide eyes to the right spot on the page.

      I’d say it’s OK to use in production for something like that.

    • Absolutely, since that would be progressive enhancement, and that is always ok for production in my book.

  7. where you come-up with all of this great ideas?

  8. You can fix the repeating navigation problem by putting it last, after the boxes and using ‘~’ to target siblings. It’s not ideal, but a little less hairy.

    http://n-son.com/misc/csstabs.html#box-one

    That demo only works in firefox though, for some reason Webkit won’t update the tabs unless you hit refresh after you click on it.

  9. Its a shame :target isn’t supported throughout IE, it can be a useful little trick for highlighting anchored links etc. But i can’t see it being used for the tab system you have shown us, its great don’t get me wrong but I think its the wrong way of going about it.

    For now javascript is simple to use and only a little extra code is needed to create pretty cool tabbed areas that allow you to go back and forth no problems with fade effects etc.

    Im sure one day soon we will be able to create full CSS/XHML websites with no external coding required and easier ways of creating tabs with effects will be used on multi platforms.

  10. This is great, there’s only one problem.
    Say that the first tab ends up being 900px high (with the text it has) and the others are around 400px. When you switch from that tab back to the other, that one’s still there, under the others.
    Also, it will move the page down if the browser window is too small.
    I tried using a method similar (and somewhat simpler) to this on a site where I could only use CSS and HTML as a description, and while it worked (it still works) it’s just better to use JS for stuff like that, in my opinion.

  11. Gringer
    Permalink to comment#

    Hey Chris,

    Can we has webkit-gradients also ? ;-)

  12. Great tutorial. Thanks for this sharing!

  13. Bradley Rosenfeld
    Permalink to comment#

    Thanks Chris!

    I can already think of some practical uses for this (in fact I was already thinking of how to take the image switcher and use the same techniques to make tabs)

  14. My friend said these are shitty.

    I think it could’ve been done better. More time and effort spent doing it. You can do better stuff Chris come on.

    • My friend says you don’t have to read this site if you don’t like it.

    • David
      Permalink to comment#

      In the words of Jemaine Clement “Could you be more constructive with your criticism?”

      These types of are simply a way to form a proof of concept. Chris even stated that this was more for informational/educational purposes than a Production environment.

      Jared, come on.

  15. Dyllon
    Permalink to comment#

    I’m curious, how can we achieve a :target selector equivalent using javasacript or jquery?

    • You can get the value of the hash with like:

      var hash = window.location.hash;

      And then write a selector to find the link (jQuery):

      $("a[href='"+hash+"']").whatever();

      or just the thing with that ID… I think this should work…

      $(hash).whatever();

    • Dyllon
      Permalink to comment#

      Actually, the jQuery anchor selector won’t work because the variable ‘hash’ will be equal to #(variable).
      $("a"+hash).whatever();
      This code however should be fine..

    • Yeah but #variable is exactly what you want, because the hash symbol is an ID selector in jQuery. So like http://www.site.com#header, window.location.hash = “#header”, which is a perfect jQuery selector. Right?

    • Dyllon
      Permalink to comment#

      yes sir, exactly what I was looking for. Thank you very much, I just wanted to point out that the anchor selector you posted wouldn’t work for that reason.

    • Dyllon
      Permalink to comment#

      The reason I cant make the href something like #variable is because the jQuery var is already set, and clicking a link with a hash tag doesn’t refresh the page. I had to link to a page to redirect to the page with the correct hash tag.

  16. Permalink to comment#

    The :target has pretty good support across the board, except as you mentioned IE. There is also a bug in Opera that has stuck around for quite some time where clicking the back button won’t trigger any changes to the :target pseudo-class.

    I’ve played with target extensively myself on my CSS3 Accordion demo and creating a CSS lightbox effect, has some pretty powerful abilities.

  17. Austin
    Permalink to comment#

    Let me try pasting that again. The forum seemed to convert it.

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US">
    <head>
    
    <style type="text/css">
    *{margin:0;padding:0px;}
    body,html{background:#ccc;text-align:center;color:black;}
    #wrapper{margin:0px auto;text-align:left;width:600px;background:white;padding:20px 0 20px 0;}
    
    #boxLinks{padding:0 0 20px 50px;overflow:hidden;}
    #boxLinks li{float:left;border-right:1px solid black;list-style-type:none;}
    #boxLinks li a{padding:0px 5px;font-weight:bold;color:#000;text-decoration:none;}
    #boxLinks li a:hover{color:#fff;background:#000;}
    
    
    #box{height:500px;width:540px;overflow:hidden;padding:0px 30px 0px 30px;}
    
    .box{height:500px;width:540px;overflow:auto;display:block;position:relative;overflow-x:hidden;}
    
    .box{display:none;}
    
    #box1:target,#box2:target,#box3:target,#box4:target{display:block;}
    
    </style>
    
    <!--[if IE]>
    <style type="text/css">
    .box{display:block;}
    #box{overflow:hidden;position:relative;}
    b{position:absolute;top:0px;right:0px;width:1px;height:501px/* #BOX height+1 */;overflow:hidden;text-indent:-999px;}
    /* Must force scroll bar to be shown user doesn't scroll into another .box */
    </style>
    <![endif]-->
    
    </head></html><body>
    
    <div id="wrapper">
    
    <ul id="boxLinks">
    	<li><a href="#box1">Tab 1</a><li><a href="#box2">Tab 2</a><li><a href="#box3">Tab 3</a><li><a href="#box4">Tab 4</a>
    </ul>
    
    <div id="box">
    	<div id="box1" class="box">Box 1! <!--[if IE]><b>.</b><![endif]--> </div>
    	<div id="box2" class="box">Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br />Box 2!<br /> <!--[if IE]><b>.</b><![endif]--> </div>
    	<div id="box3" class="box">Box 3! <!--[if IE]><b>.</b><![endif]--></div>
    	<div id="box4" class="box">Box 4! <!--[if IE]><b>.</b><![endif]--></div>
    
    
    
    </div>
    
    </div></body></html>
  18. Austin
    Permalink to comment#

    I am not 100% sure the method I posted above will work flawlessly in IE but I defiantly have gotten it to work perfectly in the past. Unfortunately I can’t find those files.

    Currently, I have been unable to get IE running using winebottler on my mac but I have successful used a windows computer(that I don’t have any more) with multiple ies to get it to work.

  19. Austin
    Permalink to comment#

    After reading over the comments, like many people said above I would never feel comfortable using the :target or hack tabbed method with most client’s website. Only in rare circumstances could it be considered helpful or suitable.

  20. TeMc
    Permalink to comment#

    @Chris: The demo is all messed up now. Aside from 2+ tabbed-area’s on 1 page not working, due to the page-length, the tabs go out of view when clicked upon.

    Example: I click Tab 3, the window jumps to that box and the tabbar goes out of view. This is not just a disadvantage but I think something that challanges the core principle of the tabbed area, to switch to other tabs.

    That’s an never accaptable loss to usability.

    Only example 3 and 6 dont have this and the rest is i.m.o. not even to be considered an option, basta !

    :) my 2c

    • No I totally agree, that “jump down” affect is a huge part of this, and the ones that jump down to the panel and cut off the tabs themselves really kill usability. But that very thing is important to see in the demo.

  21. Permalink to comment#

    As always, awesome work!

  22. Thanks Chris an other invaluable resource for any budding web designer. I came across your site recently and ever since I visit on a daily basis as there are so many snippets and tutorials that will keep me learning for some time to come. Thanks again

  23. Permalink to comment#

    Chris, Thanks for updating the example page. I’ve used your examples for testing the next current version of ie-css3 and can report that they work as expected in IE8 (but not 5.5 – 7)

    Of course, IE8 support for :target is based on the premise that developers are prepared to accept a JavaScript solution to this issue.

  24. Permalink to comment#

    Sneak idea. I think this not good time for this kind of solution :).

Leave a Comment

Current day month ye@r *

*May or may not contain any actual "CSS" or "Tricks".