How to Create a CSS3-Only Tab Control Using the :target Selector
The HTML
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>
<section id="tab2">
<h2><a href="#tab2">Tab 2</a></h2>
<p>This content appears on tab 2.</p>
</section>
<section id="tab3">
<h2><a href="#tab3">Tab 3</a></h2>
<p>This content appears on tab 3.</p>
</section>
</article>
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.
The CSS
First, we’ll style the article
container. It’s sized and has its position set to relative so we can position the sections:
article.tabs
{
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;
}
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.