Front-end Machine Coding Series #2 : Dynamic Tab Component in Vanilla JS


In modern UI design, tabs are a powerful way to organize content without overwhelming the user. While frameworks like React or Vue offer components out of the box, it's important to understand how to build such patterns from scratch.
In this blog, we'll create a tabbed interface using plain HTML, CSS, and JavaScript, where users can:
Click on a tab to view corresponding content
See a visual highlight for the active tab
Dynamically add more tabs with minimal changes
Problem Statement
Design a simple tabbed interface using Vanilla JavaScript.
Requirements:
Create three tabs labeled "Tab 1", "Tab 2", "Tab 3"
Clicking a tab should show its respective content
Highlight the active tab visually
Keep code modular, readable, and commented
Solution:
HTML Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tabs</title>
<link href="./index.css" rel="stylesheet" />
</head>
<body>
<div id="tabContainer" class="tabs"></div>
<div id="contentContainer"></div>
<script src="./tabs.js"></script>
</body>
</html>
JavaScript Code:
const tabsData = [
{
id: "tab_1",
title: "TAB 1",
content: "You are viewing the content from FIRST Tab",
},
{
id: "tab_2",
title: "TAB 2",
content: "You are viewing the content from SECOND Tab",
},
{
id: "tab_3",
title: "TAB 3",
content: "You are viewing the content from THIRD Tab",
},
];
document.addEventListener("DOMContentLoaded", () => {
//set the first Tab as default active tab
let activeTab = tabsData[0].id;
const renderTabs = () => {
//select the tabContainer & contentContainer
const tabContainer = document.querySelector("#tabContainer");
const contentContainer = document.querySelector("#contentContainer");
tabsData.forEach((tab) => {
//creating tabs
const tabButton = document.createElement("button");
tabButton.textContent = tab?.title;
tabButton.className = "tabLink";
tabButton.setAttribute("data-tab", tab?.id);
tabContainer.appendChild(tabButton);
//creating content for each tabs
const tabContent = document.createElement("div");
tabContent.innerHTML = `<h1>${tab?.title}</h1><p>${tab?.content}</p>`;
tabContent.className = "tabContent";
tabContent.id = tab?.id;
contentContainer.appendChild(tabContent);
});
//Adding one event listener on the parent of all tab-buttons (tabContainer) to handle tab switching.
tabContainer.addEventListener("click", (e) => {
// /checks if the clicked element is a tab button and has the class tabLink
if (e.target.matches(".tabLink")) {
const tabId = e.target.getAttribute("data-tab");
if (tabId != activeTab) {
openTab(tabId);
activeTab = tabId;
}
}
});
};
const openTab = (tabId) => {
const tabs = document.querySelectorAll(".tabLink");
const tabContents = document.querySelectorAll(".tabContent");
//Deactivating all tabs and contents by removing the active class.
tabs.forEach((tab) => {
tab.classList.remove("active");
});
tabContents.forEach((tab) => {
tab.classList.remove("active");
});
//activating the newly clicked tab by adding the active class.
document.getElementById(tabId).classList.add("active");
document.querySelector(`button[data-tab=${tabId}]`).classList.add("active");
};
renderTabs();
//by default
document.getElementById(activeTab).classList.add("active");
document
.querySelector(`button[data-tab=${activeTab}]`)
.classList.add("active");
});
CSS Styling:
/* Container Styling */
.tabs {
display: flex;
gap: 10px;
padding: 10px;
background-color: #f5f5f5;
border-bottom: 2px solid #ddd;
}
/* Tab Button Styling */
.tabLink {
padding: 10px 20px;
background-color: #e0e0e0;
border: none;
cursor: pointer;
font-weight: bold;
border-radius: 4px 4px 0 0;
transition: background-color 0.3s;
}
.tabLink:hover {
background-color: #ccc;
}
.tabLink.active {
background-color: #fff;
border: 2px solid #ddd;
border-bottom: none;
color: #007bff;
}
/* Content Container */
.tabContents {
border: 2px solid #ddd;
padding: 20px;
background-color: #fff;
}
/* Each Tab Content */
.tabContent {
display: none;
}
.tabContent.active {
display: block;
}
Scalability Discussion:
The beauty of this approach is that the entire tab system is driven by tabsData which is an array of objects. To add a new tab, you simply update the array:
{
id: "tab_4",
title: "TAB 4",
content: "This is dynamically added tab!",
};
The rest of the logic automatically adapts, no extra HTML or event listener setup needed.
Wrap Up
By the end of this post, you've learned how to:
Create a dynamic tabbed interface with pure JavaScript
Use data-* attributes for dynamic selection
Handle event delegation efficiently
Make your code modular, clean, and extensible
Happy coding! If you have any questions or suggestions you'd like to explore further, feel free to drop a comment below.
See you in the next blog. Please don’t forget to follow me:
Subscribe to my newsletter
Read articles from Nitin Saini directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Nitin Saini
Nitin Saini
A Full Stack Web Developer, possessing a strong command of React.js, Node.js, Express.js, MongoDB, and AWS, alongside Next.js, Redux, and modern JavaScript (ES6+)