Creating pure CSS-based tabs

I know! There are so many jQuery-plugins out there, that enable you to create dynamic tabs-functionality. One of the most widely used is probably jQuery UI’s tab-plugin ( So why bother building a new one?

Well, first of all I’m experimenting a lot with CSS right now, and secondly, I wanted to build a plugin that didn’t utilize any JavaScript at all. Don’t get me wrong – I use jQuery all the time, and I’m a big fan of it. But I want to create UI’s that download and respond extremely fast. Adding one or two jQuery plugins to a page normally doesn’t present a problem, but when you’re working with very rich and dynamic user interfaces, you often need to add several different plugins. Which means that the user will have to download lots of files, and the browser needs to perform a lot of rendering. If you can replace JavaScript with CSS, you can improve performance of the website, and you will improve the user experience.

I searched for a solution to this problem, and I found that the CSS :target pseudo-class could help me out. It is a part of the CSS3-specification and it enables you to create CSS that will respond to the part of a URL that comes after a “#”. You could – for example – create CSS that would respond to the “tab1” in the following URL:

I could write HTML like this:

<section id="tab1">
                    <a href="#tab1">Tab 1</a></h2>
                <img src="Images/blue river scenery wallpapers.jpg" alt="" /></section>

And then CSS like this:

section:target h2
    color: #FFF;
    background-color: #e21414;

The code above will change the font- and the background color of the section with the id of “tab1” when a user enters the URL

This little trick makes it very easy for us to create tabs-functionality. I have provided a small example in the following:


<article class="tabs"><section id="tab1">
                    <a href="#tab1">Tab 1</a></h2>
                <img src="Images/blue river scenery wallpapers.jpg" alt="" /></section><section id="tab2">
                <a href="#tab2">Tab 2</a></h2>
                <img src="Images/farnsworth-landscaping-firebowl5.jpg" alt="" /></section><section id="tab3">
                <a href="#tab3">Tab 3</a></h2>
                <img src="Images/Moonlit_Landscape.jpg" alt="" /></section><section id="tab4">
                <a href="#tab4">Tab 4</a></h2>
</section><section id="tab5"> <h2> <a href="#tab5">Tab 5</a></h2> <img src="Images/Moonlit_Landscape.jpg" alt="" /></section> </article>

And the CSS:

article.tabs section:target h2
    color: #FFF;
    background-color: #e21414;
    background: #F15931;
    background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2YxNTkzMSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNkNDM0MGEiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
    background: -moz-linear-gradient(top,  #F15931 0%, #D4340A 100%); /* FF3.6+ */
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#F15931), color-stop(100%,#D4340A)); /* Chrome,Safari4+ */
    background: -webkit-linear-gradient(top,  #F15931 0%,#D4340A 100%); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(top,  #F15931 0%,#D4340A 100%); /* Opera 11.10+ */
    background: -ms-linear-gradient(top,  #F15931 0%,#D4340A 100%); /* IE10+ */
    background: linear-gradient(to bottom,  #F15931 0%,#D4340A 100%); /* W3C */
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#F15931', endColorstr='#D4340A',GradientType=0 ); /* IE6-8 */

article.tabs section h2
    color: #F15B33;
    background: #eeeeee;
    background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VlZWVlZSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNjY2NjY2MiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
    background: -moz-linear-gradient(top,  #eeeeee 0%, #cccccc 100%); /* FF3.6+ */
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#eeeeee), color-stop(100%,#cccccc)); /* Chrome,Safari4+ */
    background: -webkit-linear-gradient(top,  #eeeeee 0%,#cccccc 100%); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(top,  #eeeeee 0%,#cccccc 100%); /* Opera 11.10+ */
    background: -ms-linear-gradient(top,  #eeeeee 0%,#cccccc 100%); /* IE10+ */
    background: linear-gradient(to bottom,  #eeeeee 0%,#cccccc 100%); /* W3C */
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#cccccc',GradientType=0 ); /* IE6-8 */

/* CSS to handle images and videoes in tabs content */
article.tabs section:target p
    opacity: 1;

article.tabs section p
    opacity: 0;
    -webkit-transition: opacity 1s ease-in-out;
    -moz-transition: opacity 1s ease-in-out;
    -o-transition: opacity 1s ease-in-out;
    -ms-transition: opacity 1s ease-in-out;
    transition: opacity 1s ease-in-out;

The code mentioned above is very simple. It really defines two different states for the same HTML-elements, and the matching will be based on the id of the HTML-element and the part of the URL that is after the “#”. Therefore, you can create an “active” state and a “passive” state – defined by the :target-class.

Of course, there is a lot more to the tabs-functionality than just this. But this is the most important part – the rest is primarily styling and layout. However, there are two more important things that you need to be aware of.

First of all, if you just implement the code mentioned above, you will run into a problem. You probably don’t want your users to type a difficult URL consisting of hash tags – you want the URL to be as simple as possible. But that would mean that the CSS wouldn’t be able to display the active tab – as it would have no information as to what should be the active tab.

Therefore, you need to inform the CSS by using the following JavaScript, that will append the necessary information to the URL (yeah, I know….it’s JavaScript….):

    <script type="text/javascript">
        window.location.hash = '#tab1'

Secondly, this tabs-functionality will not work in Internet Explorer 7 and 8. I have decided that I won’t optimize for Internet Explorer 7 and I’m waiting for the day when I can declare proudly that I won’t optimize for IE8 either. Meanwhile, there’s a solution in the form of a JavaScript-library called Selectivizr ( This library enables CSS3 pseudo-classes in IE7 and IE8 – however, the :target pseudo-class in Selectivizr only works in IE8.
I know that using this library will add additional JavaScript-overhead to your project – however, you can isolate the problem to those sad people using Internet Explorer by inserting the following lines of code:

<!--[if (gte IE 6)&(lte IE 8)]>
  <script type="text/javascript" src="selectivizr.js"></script>

That’s it! This new world of CSS3 continues to amaze me. 🙂

UPDATE! Why you can’t just use plain CSS!

So, I encountered one very difficult obstacle trying to implement the code above into a real life scenario. The problem is that the code above works just fine – as long as the tabs are placed at the top of the page! If you don’t place the tabs at the top, you will observe a scrolling behaviour whenever you click each tab. Not pretty, but still the default behaviour of the a tag. So nothing odd there.

I tried several approaches in order to solve this problem. One possible approach was to prevent the default behaviour of the a-tag whenever the user clicked a tab – but to no success. I therefore ended up implementing a small jQuery JavaScript to make it work. I know that this kinda ruins the idea of implementing a ‘pure’ CSS3 based tabs functionality, but I simply wasn’t able to accomplish it otherwise. I use this blog to document my findings and this time I learned that it’s not possible to create a pure CSS-based tabs-functionality without scroll. If you know how to do it, please don’t hesitate to let me know – for now, I think that the JavaScript below is a very small JavaScript-footprint.

Here’s the code for the jQuery-snippet:

    <script type="text/javascript">
      $(function() {
        $('article.tabs section div h2 a').click(function(e) {
          $('article.tabs li').removeClass("target");

Also, please read my new findings about handling Youtube-videos in tabs:


2 thoughts on “Creating pure CSS-based tabs

  1. Hi Sten,

    Your code works just fine. You can navigate to each individual section. Try clicking the headlines.

    However, you’re right about the fact that I decided to omit all of the layout CSS. Therefore, your example will not render as a tab, but will render as a list. But you still get the principle, i hope.

    I will update my blogpost as soon as possible. I will include a fully working sample.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s