Tabs are one of those UI patterns you see everywhere — dashboards, settings pages, pricing sections, docs, etc.
In this post, we’ll build a fully working tab system using only HTML, CSS, and JavaScript.
HTML Structure
The HTML Structure is minimalistic:
<div class="tabbed-content">
<ul class="tabs" role="tablist">
<li role="tab" aria-selected="true" class="active">Tab 1</li>
<li role="tab" aria-selected="false">Tab 2</li>
<li role="tab" aria-selected="false">Tab 3</li>
</ul>
<div class="content">
<div class="show">
<h2>Tab 1 content</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
</div>
<div>
<h2>Tab 2 content</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
</div>
<div>
<h2>Tab 3 content</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
</div>
</div>
</div>
As you can see, on page load, the first tab has the class name active, and the first panel has the class name show. The aria-selected attribute helps with accessibility.
The CSS Styling
.tabbed-content {
margin: 10px auto;
max-width: 500px;
}
.tabs {
display: flex;
}
.tabs li {
flex: 1;
border: 1px solid #ddd;
border-radius: 4px 4px 0 0;
background: #eee;
cursor: pointer;
height: 36px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.tabs li.active {
border-bottom: 1px solid #fff;
background: #fff;
}
.content {
padding: 10px;
border: 1px solid #ddd;
border-top: none;
}
.content > div {
display: none;
}
.content > div.show {
display: block;
}
.content h2 {
font-size: 18px;
margin-bottom: 10px;
}
.content p {
font-size: 14px;
}
The JavaScript Logic
const tabbedContent = document.querySelector('.tabbed-content');
const tabs = tabbedContent.querySelectorAll('.tabs li');
const contentItems = tabbedContent.querySelectorAll('.content > div');
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => {
// Reset all tabs
tabs.forEach((t) => {
t.classList.remove('active');
t.setAttribute('aria-selected', 'false');
});
// Hide all content
contentItems.forEach((item) => {
item.classList.remove('show');
});
// Activate clicked tab
tab.classList.add('active');
tab.setAttribute('aria-selected', 'true');
// Show matching content
contentItems[index].classList.add('show');
});
});
How It Works
We match Tab 1 to Content 1, Tab 2 to Content 2, and Tab 3 to Content 3 by using index: tabs.forEach((tab, index) => {}). So when a tab is clicked, we show the corresponding panel with contentItems[index].classList.add('show').
Before activating a new tab, we always reset everything using:
tabs.forEach(t => {
t.classList.remove('active');
});
contentItems.forEach(item => {
item.classList.remove('show');
});
This ensures only one tab is active and only one panel is displayed at a time.
Conclusion
In conclusion, each tab controls one content panel, and everything else is just state management.
No frameworks needed. You can take these tabs and style them as you need.
Top comments (0)