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

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?

CSS Master, 3rd Edition