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:
- 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;label– 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:
tabson wrapperdiv,tabs__radiooninput type="radio"elements,tabs__labelonlabelelements andtabs__contenton contentdivelements.
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:
$size– default size for padding,$background– default background color for tabs,$background--active– default background color for active tab,$color– text color for tabs,$color--disabled– 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,:checked– check ifinputis 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.
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.