JavascriptIntermediate
Tabs Component
Build a tabs component where clicking a tab shows its corresponding content and hides the others.
Tabs Component
Problem
Build a tabs component with multiple tab buttons. Clicking a tab shows its content panel and hides all others. The active tab is highlighted.
Page loads
→ First tab "HTML" is active, its content is shown
User clicks "CSS" tab
→ "CSS" tab becomes active and highlighted
→ "CSS" content panel shows
→ "HTML" content panel hides
User clicks "JavaScript" tab
→ "JavaScript" tab becomes active
→ Only "JavaScript" content is visibleLogic
- Select all tab buttons and all content panels
- Each tab button has a
data-tabattribute matching a panel'sdata-contentattribute - Listen for
clickon the tabs container using event delegation - Remove
activeclass from all tabs and panels - Add
activeclass to the clicked tab and its matching panel - On page load — activate the first tab by default
Flow
HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Tabs Component</title>
<style>
body {
font-family: sans-serif;
max-width: 520px;
margin: 60px auto;
padding: 0 20px;
}
/* tab buttons row */
.tabs {
display: flex;
gap: 6px;
border-bottom: 2px solid #e0e0e0;
margin-bottom: 20px;
}
.tab-btn {
padding: 10px 20px;
font-size: 0.95rem;
font-weight: 500;
background: none;
border: none;
cursor: pointer;
color: #888;
/* small gap so the active border lines up with the bottom border */
border-bottom: 2px solid transparent;
margin-bottom: -2px;
transition: color 0.15s, border-color 0.15s;
}
.tab-btn:hover {
color: #333;
}
/* active tab — highlighted with underline */
.tab-btn.active {
color: #111;
border-bottom-color: #111;
}
/* content panels — hidden by default */
.tab-panel {
display: none;
font-size: 0.95rem;
line-height: 1.6;
color: #555;
}
/* only the active panel is shown */
.tab-panel.active {
display: block;
}
.tab-panel h3 {
margin-top: 0;
color: #222;
}
</style>
</head>
<body>
<h1>Languages</h1>
<!-- tab buttons — data-tab matches each panel's data-content -->
<div class="tabs">
<button class="tab-btn active" data-tab="html">HTML</button>
<button class="tab-btn" data-tab="css">CSS</button>
<button class="tab-btn" data-tab="js">JavaScript</button>
</div>
<!-- content panels -->
<div class="tab-panel active" data-content="html">
<h3>HTML</h3>
<p>
HTML stands for HyperText Markup Language. It is the standard
markup language used to structure content on the web — headings,
paragraphs, links, images, and more.
</p>
</div>
<div class="tab-panel" data-content="css">
<h3>CSS</h3>
<p>
CSS stands for Cascading Style Sheets. It is used to style and
layout HTML elements — colors, spacing, fonts, positioning, and
responsive design.
</p>
</div>
<div class="tab-panel" data-content="js">
<h3>JavaScript</h3>
<p>
JavaScript is a programming language that adds interactivity to
web pages — handling clicks, form validation, animations, and
communication with servers.
</p>
</div>
<script src="script.js"></script>
</body>
</html>Solution
// Step 1 — select the tabs container, all tab buttons, and all panels
const tabsContainer = document.querySelector(".tabs");
const tabButtons = document.querySelectorAll(".tab-btn");
const tabPanels = document.querySelectorAll(".tab-panel");
// Step 2 — use event delegation on the tabs container
// one listener handles clicks on any tab button
tabsContainer.addEventListener("click", (e) => {
// ignore clicks that are not on a tab button
if (!e.target.classList.contains("tab-btn")) return;
const clickedTab = e.target;
// Step 3 — read which tab was clicked from data-tab
const targetName = clickedTab.dataset.tab;
// Step 4 — switch to that tab
switchTab(targetName);
});
function switchTab(targetName) {
// Step 5 — remove "active" from all tab buttons
tabButtons.forEach((btn) => btn.classList.remove("active"));
// Step 6 — remove "active" from all content panels
tabPanels.forEach((panel) => panel.classList.remove("active"));
// Step 7 — find the tab button and panel that match targetName
// [data-tab="..."] selector finds the element with matching attribute
const matchingTab = document.querySelector(`[data-tab="${targetName}"]`);
const matchingPanel = document.querySelector(`[data-content="${targetName}"]`);
// Step 8 — activate both
matchingTab.classList.add("active");
matchingPanel.classList.add("active");
}Code Execution
Trace through clicking the "CSS" tab:
| Step | Code | Result |
|---|---|---|
| Click target | e.target | <button class="tab-btn" data-tab="css"> |
| Check class | e.target.classList.contains("tab-btn") | true |
| Read data-tab | clickedTab.dataset.tab | "css" |
| Remove active from tabs | tabButtons.forEach(...) | all tabs un-highlighted |
| Remove active from panels | tabPanels.forEach(...) | all panels hidden |
| Find matching tab | [data-tab="css"] | CSS button |
| Find matching panel | [data-content="css"] | CSS panel |
| Add active | both elements get .active | CSS tab highlighted, CSS panel shown |
Trace through clicking somewhere that is not a tab (e.g. inside a panel):
| Step | Code | Result |
|---|---|---|
| Click target | e.target | some <p> or <h3> inside a panel |
| Check class | e.target.classList.contains("tab-btn") | false |
| Action | return — nothing happens | no change |
Output
Page loads → "HTML" tab active, HTML content shown
Click "CSS" → "CSS" tab active, CSS content shown, HTML hidden
Click "JavaScript" → "JavaScript" tab active, only JS content shown
Click "HTML" again → back to HTML content