
Tabs are one of those UI patterns you see everywhere — dashboards, settings pages, pricing sections,...
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.
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.
.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;
}
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');
});
});
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.
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.
gemmaI ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...
communityHey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...
ai(yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...
aiMy laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...
githubactionsI Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...
aiI've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...
Workflows from the Neura Market marketplace related to this Midjourney resource