
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.
aiMost of us have seen a coding agent fail to complete a task we know it can do. We just don't...
googlecloudWhen building Generative AI applications, developers often encounter a massive bottleneck: sequential...
discussI’ve been thinking about sharing some electronic circuit posts on Dev.to — small circuits, DIY...
agentsWhat nobody tells you about exporting your multi-agent prototype to a local workspace. Every...
agenticarchitectAutonomous agents are genuinely good at answering messy business questions. Give one an LLM and a set...
aiPR volume went up, ticket quality didn't, and the gap got filled with LLMs on both sides of the review: bots reviewing, bots replying, bots occasionally arguing with bots about priorities that only existed in a teammate's head. Our CEO named the actual problem, and it's bigger than code review.
Workflows from the Neura Market marketplace related to this Stable Diffusion resource