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:
tabs
on wrapperdiv
,tabs__radio
oninput type="radio"
elements,tabs__label
onlabel
elements andtabs__content
on contentdiv
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:
<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 ifinput
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.
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.