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

Nitin SainiNitin Saini
3 min read

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:

Twitter
LinkedIn

0
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+)