The appearence of “tab” navigation is relatively easy to create, but making a tab bring up its own content without moving to a new page is more complex, especially if you want to activate it on click, rather than hover… particularly if you are determined to only use CSS. Thankfully, the :target
selector can do much of the work for us.
First, let’s create some basic markup. Let’s say we were going to create a series of slides about some of Saturn’s moons: Titan, Mimas, Enceladus and Iapetus. I’ll use markup similar to the simple CSS image gallery: the names of the moons are clickable definition terms, and the explanation of each is a definition declaration.
<dl id="moons">
<dt><a href="#titan">Titan</a>
<dd id="titan"><img src="titan.jpg" alt="Titan">
<p>Saturn's largest moon is unique in several respects.
</dd>
<dt><a href="#iapetus">Iapetus</a>
<dd id="iapetus"><img src="iapetus.jpg" alt="Iapetus">
<p>Iapetus is the third largest moon of Saturn.</p>
</dd>
</dl>
Note the id
value on each <dd>,
and the href
links within each <dt>
. These go to anchor links, just as we have explored in the past. Next, the associated CSS to produce the basic appearance of the tabs: I've written this in Sass, but vanilla CSS would work too:
$bgcolor: #222;
* {
box-sizing: border-box;
}
body {
margin: 3rem;
background: $bgcolor;
}
dl#moons {
line-height: 1.5;
font-family: Avenir, sans-serif;
position: relative;
width: 50rem;
dt {
display: inline-block;
margin-right: .5rem;
position: relative;
z-index: 5;
a {
text-decoration: none;
padding: 1rem;
border: 1px solid black;
background: #555;
display: block;
min-width: 8rem;
font-weight: 600;
border-radius: 10px 10px 0 0;
&:hover, &:focus {
background: #222;
}
&:visited {
background: #aaa;
}
}
}
Then the <dd>
elements and their content. I've added a border-top
to the <dd>
elements to provide the impression that the tabs are positioned above the <dd>
tags; in reality, they both start at the same point. This is done to counteract the jump behavior associated with anchor links: without it, the page would scroll instantly to the position of the associated panels after a click, obscuring the tabs. As it is, activated tabs will always be forced to the very top of the page.
I've also provided the images with a circle wrap to make the best use of the limited space inside each info window.
dd {
position: absolute;
left: 0;
margin-left: 0;
top: 0;
padding: 2rem 1rem 1rem 1rem;
border-top: 3.6rem solid $bgcolor;
width: 100%;
background: #000;
color: white;
opacity: 0;
transition: .3s opacity;
a {
color: white;
}
img {
max-width: 100%;
float: right;
margin: 1rem;
shape-outside: circle();
}
}
}
target
makes CSS aware of the relationship between elements with id
attributes and links to those elements. One opportunity this affords us to change the state of the target when the associated link is clicked on: in this case, its z-index
and opacity
:
dd#titan:target, dd#iapetus:target, dd#enceladus:target,dd#mimas:target {
z-index: 4;
opacity: 1;
}
We could make any of the information windows appear by default by sharing the appropriate anchor in a URL; for example: http://thenewcode.com/291/Pure-CSS-Tab-Navigation#iapetus
Alternatively, we could use JavaScript to activate one of the tabs by default:
document.querySelector('a[href="#titan"]').click();
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/GJdPeW