CSS-based tabs, take 2

An earlier post detailed how we can build a 100% CSS-based tab layout. While pretty cool and somewhat usable it had some flaws.

  • Jumps back to the first tab when the mouse pointer leaves the area.
  • Depends on exact positioning of the content in relation to the tabs.
  • Not touch friendly.
  • Not keyboard friendly.

Most of this is a result of the usage of hover. This can be fixed by using the checkbox hack to activate the tabs. This technique is often used to create small screen friendly menus but is not limited to that. To do this we have to use label elements for the tabs and a corresponding radio button that will be hidden with CSS. The resulting HTML code looks like this:

<div class="cssv-widgets">

  <div class="cssv-widget">
    <input type="radio" name="widget" id="widget1" checked="checked" />
    <label for="widget1" class="caption" onclick>Widget #1 caption</label>
    <div class="content">
      <p>Widget #1 content</p>

  <div class="cssv-widget">
    <input type="radio" name="widget" id="widget2" />
    <h2 class="caption"><label for="widget2" onclick>Widget #2 caption</label></h2>
    <div class="content">
      <p>Widget #2 content</p>

  <div class="cssv-widget">
    <input type="radio" name="widget" id="widget3" />
    <label for="widget3" class="caption" onclick>Widget #3 caption</label>
    <div class="content">
      <p>Widget #3 content</p>


Note that we use radio buttons, not checkboxes. This is important because we want one and only one tab to be active at a time. And the first radio button has the checked attribute so the first tab is active by default. There are some browser problems to take care of later, but the basic CSS we will use for the checkbox hack is like this:

/* Hide the radio buttons */
.cssv-widget > input[type=radio] {
  position: absolute;
  left: -9999px;
/* Display content for the selected tab */
.cssv-widget > input[type=radio]:checked ~ .content {
  display: block;

This is a very specific combination of direct children and sibling selectors. This will, in combination with our html skeleton, ensure that nothing from the checkbox hack is applied to anything inside the .content containers.

Note: When clicking the label element, the corresponding radio button is checked and current Opera and Firefox versions (and possibly other browsers) will then try to scroll the page so we can see the radio button we checked. If we position the radio buttons off screen with top: -9999px; that will send us to the top of the page. So we will only use horisontal positioning to move the radio buttons off screen.

Note 2: The onclick attribute is recommended on the labels due to a bug in iOS < 6.0 . Most Apple device users are on 6.0 or newer these days but it doesn't seem to create any trouble.

Note 3: Due to a sibling selector/pseudo-class bug existent in Webkit < 535.3 (corresponding to Android <= 4.1.2) we need some magic code. Android <= 4.1.2 is currently most Android users and I don't know of any side effects from this so we will keep this hack.

body { 
  -webkit-animation: bugfix infinite 1s; 
@-webkit-keyframes bugfix { 
  from {padding:0;} 
  to {padding:0;} 

Source for note 2 and 3: Advanced Checkbox Hack by Tim Pietrusky.

After taking all this into consideration we end up with our tabs:

Widget #1 content

Widget #2 content

Widget #3 content

These tabs don't jump back to the first one when you leave the area, it is fully usable on a touch device and can be navigated via a keyboard. So 3 of the 4 problems mentioned in the beginning are solved and it is still 100% HTML/CSS. There is still absolute positioning involved so we need to set a fixed height with CSS to avoid crashing into content below, but we are no longer dependent on not having any room between the captions and the content. So it's still not perfect but it's almost there.

CSS code for this post


Post a comment

You may use the following HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>