How to Build a Responsive Tab Component With CSS and a Touch of JavaScript
In this tutorial, we’ll learn how to build a responsive tab component with CSS and a little bit of JavaScript. It’s absolutely possible to create pure CSS tab components, but for this example let’s put our JavaScript skills into practice.
Here's what we're going to build, in a few large steps:
Note:
1. The HTML
To begin with, let’s examine the required markup. We have a container which includes the tabs themselves (list items), as well as the content for each tab (tab panels). To associate a tab with the corresponding panel we use the data-index custom attribute which holds a unique value for each tab panel. That said, because of Zero-based numbering, a panel with data-index = 0 is associated with the first tab, a panel with data-index = 1 is associated with the second, and so on.
Here’s the HTML markup:
1 |
<div class="tabs-container"> |
2 |
<ul class="tabs"> |
3 |
<li class="active"> |
4 |
<a href="">Part 1</a> |
5 |
</li>
|
6 |
<li>
|
7 |
<a href="">Part 2</a> |
8 |
</li>
|
9 |
<li>
|
10 |
<a href="">Part 3</a> |
11 |
</li>
|
12 |
</ul>
|
13 |
<div class="tabs-content"> |
14 |
<div class="tabs-panel active" data-index="0"> |
15 |
<!-- content here -->
|
16 |
</div>
|
17 |
<div class="tabs-panel" data-index="1"> |
18 |
<!-- content here -->
|
19 |
</div>
|
20 |
<div class="tabs-panel" data-index="2"> |
21 |
<!-- content here -->
|
22 |
</div>
|
23 |
</div>
|
24 |
</div>
|
2. The CSS
As a next step, let’s specify a few CSS rules for our component. Nothing too fancy, just some basic styles. One thing to note is that we don’t use any transitions (e.g. fade, slide) to toggle between the tab panels; instead these appear and disappear with a simple on/off switch.
Here are the initial styles:
1 |
.tabs-container { |
2 |
max-width: 1000px; |
3 |
margin: 50px auto; |
4 |
padding: 25px; |
5 |
}
|
6 |
|
7 |
.tabs { |
8 |
display: flex; |
9 |
}
|
10 |
|
11 |
.tabs li:not(:last-child) { |
12 |
margin-right: 7px; |
13 |
}
|
14 |
|
15 |
.tabs li a { |
16 |
display: block; |
17 |
position: relative; |
18 |
top: 4px; |
19 |
padding: 10px 25px; |
20 |
border-radius: 2px 2px 0 0; |
21 |
background: white; |
22 |
opacity: 0.7; |
23 |
transition: all 0.1s ease-in-out; |
24 |
}
|
25 |
|
26 |
.tabs li.active a, |
27 |
.tabs li a:hover { |
28 |
opacity: 1; |
29 |
top: 0; |
30 |
}
|
31 |
|
32 |
.tabs-content { |
33 |
position: relative; |
34 |
z-index: 2; |
35 |
padding: 25px; |
36 |
border-radius: 0 4px 4px 4px; |
37 |
background: white; |
38 |
}
|
39 |
|
40 |
.tabs-panel { |
41 |
display: none; |
42 |
}
|
43 |
|
44 |
.tabs-panel.active { |
45 |
display: block; |
46 |
}
|
3. The JavaScript
With the HTML and CSS in place, it’s time to look at the required JavaScript code.
Each time we click on a tab, we do the following things:
- Remove the
activeclass from the corresponding tab (by default the first one) and the associated tab panel (by default the first one). - Find the
liparent of this tab, add theactiveclass to it, and retrieve its index. - Find the tab panel whose attribute value (for the
data-indexattribute) matches the aforementioned index value and assign theactiveclass to it.
Here’s the resulting JavaScript code:
1 |
const tabLinks = document.querySelectorAll(".tabs a"); |
2 |
const tabPanels = document.querySelectorAll(".tabs-panel"); |
3 |
|
4 |
for(let el of tabLinks) { |
5 |
el.addEventListener("click", e => { |
6 |
e.preventDefault(); |
7 |
|
8 |
document.querySelector('.tabs li.active').classList.remove("active"); |
9 |
document.querySelector('.tabs-panel.active').classList.remove("active"); |
10 |
|
11 |
const parentListItem = el.parentElement; |
12 |
parentList.classList.add("active"); |
13 |
const index = [...parentListItem.parentElement.children].indexOf(parentListItem); |
14 |
|
15 |
const panel = [...tabPanels].filter(el => el.getAttribute("data-index") == index); |
16 |
panel[0].classList.add("active"); |
17 |
});
|
18 |
}
|
4. Going Responsive
Our component is almost ready! The last thing we have to do is to make the component responsive. So, for instance, when the viewport has a maximum width of 600px, it should collapse and look like this:

As we’re using a desktop-first approach, these are the CSS rules that we have to overwrite:
1 |
@media screen and (max-width: 600px) { |
2 |
.tabs { |
3 |
flex-direction: column; |
4 |
}
|
5 |
|
6 |
.tabs li { |
7 |
width: 100%; |
8 |
}
|
9 |
|
10 |
.tabs li:not(:last-child) { |
11 |
margin-right: 0; |
12 |
}
|
13 |
|
14 |
.tabs li a { |
15 |
border-radius: 0; |
16 |
opacity: 1; |
17 |
top: 0; |
18 |
}
|
19 |
|
20 |
.tabs li.active a::before { |
21 |
content: '•'; |
22 |
padding-right: 5px; |
23 |
}
|
24 |
|
25 |
.tabs-content { |
26 |
border-radius: 0; |
27 |
}
|
28 |
}
|
5. Browser Support
Our demo works well in all recent browsers and devices. As usual with my tutorials, we use Babel to compile the ES6 code down to ES5.
Conclusion
In this short tutorial, we managed to create a useful responsive tab component with HTML, CSS, and JavaScript. Again, this component isn’t properly accessible, but if you want to enhance its functionality, that would be a good next step. Happy coding!











