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.

CSS tabs could be accessible, read about it here.

HTML structure

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

Let’s break it down by elements:

wrapper - this element is used to distinguish tabs from the rest of the content;

input type="radio" - this element will be hidden, but will be used as a controlling element;

- this element will be hidden, but will be used as a controlling element; label - this element will be used as a clickable tab;

- this element will be used as a clickable tab; content - this element is used as a wrapper for tab’s content.

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:

tabs on wrapper div ,

on wrapper , tabs__radio on input type="radio" elements,

on elements, tabs__label on label elements and

on elements and tabs__content on content div elements.

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:

1

` < 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:

1

2

< 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:

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:

$size - default size for padding,

- default size for padding, $background - default background color for tabs,

- default background color for tabs, $background--active - default background color for active tab,

- default background color for active tab, $color - text color for tabs,

- text color for tabs, $color--disabled - text color for disabled tabs and

- text color for disabled tabs and $breakpoint - width which will define our tabs layout.

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:

nth-of-type - selects the nth child of the same elements,

- selects the nth child of the same elements, :checked - check if input is checked and

- check if is checked and ~ - selects siblings selector.

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.

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.