How to make tabs using only CSS

I know there are more than a few articles about this topic. And there are 2 basic approaches: using :target pseudo selector and using list with :checked pseudo selector.

I prefer the second approach, but without list or nested div structure.

HTML structure

Let’s start with HTML.  This is the full structure:

See the Pen CSS tabs HTML structure by CiTA (@CiTA) on CodePen.

Let’s break it down by elements:

This structure may look a bit dirty, but soon you’ll see the benefit of it. The basic principle is to group different types of elements.

Next we’ll add the following classes on every element:

BEM naming convention is used for this purpose.

To make sure every input type="radio" element is a part of the same block, we’ll add name attribute with same value on it like this:

<input class="tabs__radio" name="myTabs" />

Labels are generally used to define an input element. If for attribute is provided with matching id of an input, they will be bound together. If you click on a label that is related to input type="radio", checked state of an element will be toggled. This will be used as a trigger for changing tabs.

With that clarified, we’ll add unique id attributes on every input type="radio" and matching for attributes to every label like this:

<input class="tabs__radio" id="myTab1" name="myTabs" />

<label class="tabs__label for="myTab1">

Finally, we’ll add value attribute for every input type="radio" element and checked attribute on an element which should be active.

CSS code

To create styling for tabs, SCSS and cita-flex will be used. This is the final code:

See the Pen CSS tabs HTML structure and styling by CiTA (@CiTA) on CodePen.

First we will import cita-flex mixins in our file. It is a small library which could help you create layouts using flexbox built by me. cita-flex is available through bower and you could install it using command bower install cita-flex.

After that we should define default variables which will help us write more consistent code. There are 6 variables:

I really like BEM naming convention and I use it for defining CSS variables, too.

Wrapper element should be displayed as a wrapped flex.

input type="radio" elements should be hidden. Here we hide them using position: absolute technique and push the elements outside of the viewport.

Tabs, or label elements in this case, are flex items. They are aligned in a row and have fluid width controlled by flex-basis.

Tab’s content is an element which takes 100% of the wrapper’s width. This is achieved by setting flex-basis to 100%. By default, content is hidden unless matching input type="radio" is checked.

Now for the fun part, using CSS to control the tabs. We will take advantage of 3 powerful CSS selectors:

If the first child of a input type="radio" is checked, the first tab should be active and the content of the first tab should be displayed.

Easy, we’ll use .input__radio:nth-of-type(1) to select the first input type="radio". Then we’ll check if input is checked:  .input__radio:nth-of-type(1):checked and find the first tab using siblings selector: .input__radio:nth-of-type(1):checked ~ .tabs__label:nth-of-type(1). Finally, we’ll find the content of the first tab: .input__radio:nth-of-type(1):checked ~ .tabs__content:nth-of-type(1).

Now that we know how to do this for first tab, we could use @for loop and repeat this for every tab. And that’s it!

Bonus: disabled state

I’ve had situations where tabs should be disabled. It is legit situation and for this purpose I’ve added disabled state of tab.

We’ll use :disabled pseudo selector and hide-if-disabled class for elements that should be hidden.

The principle is the the same: we’ll find disabled input element and matching tab and content: .tab__radio:nth-of-type(1):disabled ~ .hide-if-disabled:nth-of-type(1).

Now we could repeat this for every tab using @for loop and we’re finished.

Below you could see the full solution with disabled tabs 2 and 10.

See the Pen CSS tabs HTML structure and styling with disabled state by CiTA (@CiTA) on CodePen.

Final thought

Full demo is available on Github and via bower: bower install csstabs.

Do you find this solution usable, because I really like how we could do even more complex things with CSS only nowdays?

Make sure you follow me on Twitter and Medium, more posts are coming soon.