By Craig Buckler

How to Create a CSS3-Only Tab Control Using the :target Selector

By Craig Buckler

Do we need another tab control? Probably not, but this example demonstrates the power of the CSS3 :target selector. We’re going to build an attractive animated tab control using HTML5 and CSS3. You won’t need JavaScript or images and it works in IE9, Chrome, Firefox, Safari and Opera.

Essential Features

You’ll find many CSS3-only tab controls throughout the web. However, many have issues such as:

  • not displaying any tab content if you link to the page without a hash selector, i.e. you link to mypage.html rather than mypage.html#tab1.
  • not highlighting the active tab.

This solution overcomes those problems — view the demonstration page…

What about IE6, 7 and 8?

Were you expecting modern CSS3 effects to work in a browser released in 2001? IE7 and 8 will show the first tab only. IE6 shows the last tab, although it’d be easy to set it to the first.

You therefore have two options:

  1. Don’t offer legacy browser support. IE users won’t know they’re missing anything so, if the content’s not vital to your page, you could choose to ignore them.
  2. But that’s not nice. The quickest solution is to add the selectivizr shim. Alternatively, resort to a better progressively enhanced solution and forget about this flaky CSS3 nonsense.


Here’s our basic HTML5 code. Tab content is contained within a section. The tab itself is the first child and defined as an h2 element with an inner link to the outer section:

<article class="tabs">

	<section id="tab1">
		<h2><a href="#tab1">Tab 1</a></h2>
		<p>This content appears on tab 1.</p>
	<section id="tab2">
		<h2><a href="#tab2">Tab 2</a></h2>
		<p>This content appears on tab 2.</p>
	<section id="tab3">
		<h2><a href="#tab3">Tab 3</a></h2>
		<p>This content appears on tab 3.</p>


This is different to HTML tab code you’ve seen before. The majority of controls define the tabs as a ul list followed by each content section. Although it’s possible to use similar mark-up, it makes tab highlighting far more difficult because the tab itself can’t be styled using :target. The best solution I found was to add a pseudo-element to the section which was colored accordingly and positioned under the tab text. That quickly became a convoluted mess.


First, we’ll style the article container. It’s sized and has its position set to relative so we can position the sections:

	position: relative;
	display: block;
	width: 40em;
	height: 15em;
	margin: 2em auto;

This is followed by the sections. They’re all absolutely positioned 1.8em from the top to allow room for the tabs. The box-shadow is fairly light because each section is stacked on top of one another:

article.tabs section
	position: absolute;
	display: block;
	top: 1.8em;
	left: 0;
	height: 12em;
	padding: 10px 20px;
	background-color: #ddd;
	border-radius: 5px;
	box-shadow: 0 3px 3px rgba(0,0,0,0.1);
	z-index: 0;

Since the last tab will be shown on top, we’ll switch it to the first tab by setting a higher z-index:

article.tabs section:first-child
	z-index: 1;

We can now style the tabs. These are colored in their ‘off’ state and positioned higher than our sections. The left positions of the second and third tabs are adjusted to ensure they’re not overlaying each other.

article.tabs section h2
	position: absolute;
	font-size: 1em;
	font-weight: normal;
	width: 120px;
	height: 1.8em;
	top: -1.8em;
	left: 10px;
	padding: 0;
	margin: 0;
	color: #999;
	background-color: #ddd;
	border-radius: 5px 5px 0 0;

article.tabs section:nth-child(2) h2
	left: 132px;

article.tabs section:nth-child(3) h2
	left: 254px;

article.tabs section h2 a
	display: block;
	width: 100%;
	line-height: 1.8em;
	text-align: center;
	text-decoration: none;
	color: inherit;
	outline: 0 none;

All our tabs and sections are now defined and tab 1 is shown by default even when none of the sections are targeted in the URL. We can now change the color, background-color and z-index of the active section using the :target selector:

article.tabs section:target,
article.tabs section:target h2
	color: #333;
	background-color: #fff;
	z-index: 2;

As a bonus, let’s add a transition effect when the targeted tab is changed:

article.tabs section,
article.tabs section h2
	-webkit-transition: all 500ms ease;
	-moz-transition: all 500ms ease;
	-ms-transition: all 500ms ease;
	-o-transition: all 500ms ease;
	transition: all 500ms ease;

View the demonstration page…

Unlike many JavaScript solutions, our CSS3 widget retains the full history of tab views so the browser back and next buttons operate correctly. It’s also possible to link directly to a tab from anywhere in the page — as illustrated by the ‘NEXT’ links.

It’s not perfect; when you initially link to the page, the first tab is active but shown in the ‘off’ state. It’s difficult to overcome that issue … unless you can think of a solution? The easiest fix would simply make the ‘on’ and ‘off’ state differences a little more subtle.

It doesn’t end there, either. Because we’re using CSS, we can transform the tab control into other widgets without altering the HTML mark-up. Stay tuned to SitePoint…

If you enjoyed reading this post, you’ll love Learnable; the place to learn fresh skills and techniques from the masters. Members get instant access to all of SitePoint’s ebooks and interactive online courses, like Learn CSS3.

Comments on this article are closed. Have a question about CSS3? Why not ask it on our forums?

  • Nice post. You removed outline:none from the tab links. As you know, you should rarely remove the outline as it gives orientation for keyboard only users. If you do remove it, create a fallback, using the :focus pseudo-selector on the tab links.

  • I’ve been working a lot with :target in the last week for the mobile version of to create a drop down menu in the same style as Facebook but without the need for JavaScript or a page refresh.

    You can make the code fail more gracefully with the use of the negation pseudo class as the browsers that don’t support :target also don’t support :not() you can work around the code so that content is only hidden in the tabs system if the browser supports :not().

    Also if you use display:none in addition to/instead of z-index and remove the fixed height then the tabs will be able to take any amount of content you put into them. Restricting the height may be good on feature panels but other sites will probably be broken very quickly by content writers.

    You could fix the default tab state when the page is first loaded by reversing the order of the tabs in the code then styling the default tab as active before then overriding it with the sibling ~ selector to set all tabs after a :target tab as not active. Not ideal semantically but a solution.

  • My strong opinion, again: this belongs with javascript.

    Using fragment identifiers for the wrong reasons equals bad UX. Try the back button like the frustrated user does…

    • I agree JavaScript can make a better tab control, but there’s no reason why you couldn’t use this as a starting point. It would then remain functional for those without JavaScript.

      In addition, I’m not convinced about the back button argument. If you’re using a tab control to show content, I think it’s a bonus the back button works correctly. It’s only when you’re using it in an application interface that it makes less sense.

      • The right question for me it’s not about whether one can do it, it’s about whether we should do it.

        Some try to make a kind of chicken and egg thing about when it comes to “modern” interfaces. It’s really not like that.

        Tabs are enhancement of the basic web interface. That means that the web page (or app, for that matter, there’s no difference here), will have to basically start without them.

        Right, as a bonus, javascript makes this enhancement possible. And now CSS3 makes it possible too.That doesn’t mean I should go backwards, start with tabs, then find fallback after fallback, is it?

        My conclusion: no fallback for those without javascript, no CSS3 “abominations” just because one can :)

        … since we build from the ground up, do we, not the other way around!

      • Progressive enhancement is the key here. You can provide a working “tab” control if the user has just HTML, HTML+CSS or HTML+CSS+JavaScript. (And you don’t need HTML5, CSS3 or the :target selector).

        Don’t look at it providing fallbacks – it’s about starting with the lowest common denominator and enhancing the functionality when a browser supports it.

      • You mean graceful degradation, right? ;)

  • This is a really neat tutorial; thanks for sharing.

    For me, it’s a big usability problem to not have the active tab highlighted on page load. It’s enough of a reason to forgo a CSS-only solution, which seems more neat than useful. Furthermore, I’m not convinced CSS should be used in this way, as others have pointed out. We may look back at ourselves some years hence and find the whole “CSS should be strictly presentational” argument primitive, but for now I worry that using CSS to enhance a page’s functionality (vs. just its presentation) is setting a bad precedent.

    That said, it’s very cool to stretch the limits and see what CSS3 is capable of.

  • Em Comments

    You lost me at: “IE7 and 8 will show the first tab’s content only.”

    • Chris

      You had me at hello.

  • Hello, I was playing around with this code and it appears that when you adjust the size of the tab boxes (width and height) that the third tab actually is peaking out and is halfway visible in the other two tabs.

    Any thoughts?

    (I am a college student and only have limited knowledge of HTML5 and CSS3.)

  • Francis

    I think for now to resolve the issue of tab1 going ‘off state’, just attach the #tab1 at end of url. For example tabs.html#tab1.

  • Kev

    I’ve been meaning to have a play with CSS-only tabs, so your article inspired me.


    btw, using Rachel’s ideas, I have done this – – which highlights 1st tab as current, by having 1st tab markup LAST.

  • Jake P

    A quick technical support question. I have utilized this CSS coding within a WP page post. When a user clicks on the tab, it causes the content to jump to the top of the container, re-orienting the user’s place on the page. Could this be a HTML5/CSS3 conflict with WP or a conflict with with my .js layout? –> “What We Are About” tab.

    Thanks for your time! Great tutorial.

    • The tabs are linking to content fairly low down your page therefore the browser jumps it up to be nearer the top. You could prevent that, but would need a little JavaScript to do it.

Get the latest in Front-end, once a week, for free.