1. Underline Tabs — classic with a colored underline on active
Overview
Content for the active tab appears here. Click above to switch.
<div class="bar"> <button class="active">Overview</button> <button>Specs</button> <button>Reviews</button> <button>Support</button> </div> <div class="panel"> <h4>Overview</h4> <p>Content for the active tab appears here. Click above to switch.</p> </div>
.bar { display: flex; border-bottom: 1px solid #eee; } .bar button { padding: 12px 16px; background: transparent; border: none; color: #666; border-bottom: 2px solid transparent; margin-bottom: -1px; /* overlap parent border */ } .bar button.active { color: #ec4899; border-bottom-color: #ec4899; font-weight: 600; }
// Click a tab — make it active, deactivate the others. // This same handler works for variants 1, 2, 3, 5, 6, 7. const bar = document.querySelector('.bar'); const buttons = bar.querySelectorAll('button'); buttons.forEach(btn => { btn.addEventListener('click', () => { buttons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); }); });
2. Pill Tabs — segmented control style, great for filters
Segmented pill tabs — iOS-style. One active at a time.
<div class="bar"> <button class="active">All</button> <button>Popular</button> <button>Newest</button> <button>Following</button> </div> <div class="panel"><p>Segmented pill tabs — iOS-style. One active at a time.</p></div>
.bar { display: inline-flex; gap: 4px; background: #f4f4f5; padding: 4px; border-radius: 999px; } .bar button { padding: 8px 16px; border-radius: 999px; background: transparent; border: none; font-weight: 600; } .bar button.active { background: #111; color: #fff; }
3. Vertical Tabs — settings-page style, left rail of sections
Profile settings
Your public profile, avatar, name, and bio.
<div class="settings"> <div class="bar"> <button class="active"><i class="ph ph-user"></i> Profile</button> <button><i class="ph ph-bell"></i> Notifications</button> <button><i class="ph ph-shield"></i> Security</button> <button><i class="ph ph-credit-card"></i> Billing</button> <button><i class="ph ph-gear"></i> Preferences</button> </div> <div class="panel"> <h4>Profile settings</h4> <p>Your public profile, avatar, name, and bio.</p> </div> </div>
.settings { display: grid; grid-template-columns: 180px 1fr; gap: 22px; } .bar { display: flex; flex-direction: column; gap: 4px; } .bar button { text-align: left; padding: 10px 14px; border-radius: 8px; } .bar button.active { background: #ec4899; color: #fff; }
4. Sliding Indicator — animated pill slides between tabs on click
The indicator smoothly animates with CSS transitions.
<div class="bar"> <span class="indicator"></span> <button class="active">Monthly</button> <button>Quarterly</button> <button>Yearly</button> </div> <div class="panel"><p>The indicator smoothly animates with CSS transitions.</p></div>
.bar { position: relative; } .indicator { position: absolute; top: 4px; bottom: 4px; background: linear-gradient(135deg, #ec4899, #8b5cf6); border-radius: 999px; transition: all .35s cubic-bezier(.2,.8,.3,1); z-index: 0; } .bar button { position: relative; z-index: 1; } .bar button.active { color: #fff; }
// Position indicator over the active button const bar = document.querySelector('.bar'); const indicator = bar.querySelector('.indicator'); const updateIndicator = (btn) => { indicator.style.left = btn.offsetLeft + 'px'; indicator.style.width = btn.offsetWidth + 'px'; }; bar.querySelectorAll('button').forEach(btn => { btn.addEventListener('click', () => { bar.querySelectorAll('button').forEach(b => b.classList.remove('active')); btn.classList.add('active'); updateIndicator(btn); }); }); updateIndicator(bar.querySelector('.active'));
5. Icon Tabs — icon + label stacked, mobile-app style
Icons make tabs scannable at a glance — especially in compact spaces.
<div class="bar"> <button class="active"><i class="ph-fill ph-house"></i>Home</button> <button><i class="ph ph-compass"></i>Discover</button> <button><i class="ph ph-chat-circle"></i>Messages</button> <button><i class="ph ph-user-circle"></i>Profile</button> </div> <div class="panel"><p>Icons make tabs scannable at a glance — especially in compact spaces.</p></div>
.bar button { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 14px 10px; } .bar button i { font-size: 1.4rem; }
6. Badge Tabs — unread / count pill next to label
Perfect for mail apps, notifications, and task lists.
<div class="bar"> <button class="active">Inbox <span class="badge">12</span></button> <button>Drafts <span class="badge">3</span></button> <button>Archive</button> <button>Spam <span class="badge">124</span></button> </div> <div class="panel"><p>Perfect for mail apps, notifications, and task lists.</p></div>
.badge { background: #e5e7eb; color: #333; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 999px; } /* Active tab's badge pops with brand color */ .bar button.active .badge { background: #ec4899; color: #fff; }
7. Card Tabs — stacked labels + sublabels inside rounded cards
Works well for pricing, plan selectors, or multi-option configurators.
<div class="bar"> <button class="active">Starter<small>Free</small></button> <button>Pro<small>$9/mo</small></button> <button>Team<small>$29/mo</small></button> </div> <div class="panel"><p>Works well for pricing, plan selectors, or multi-option configurators.</p></div>
.bar { display: flex; gap: 6px; padding: 4px; background: #fef3c7; border-radius: 12px; } .bar button { flex: 1; padding: 12px 10px; text-align: left; border-radius: 8px; } .bar button small { display: block; font-size: 11px; color: #a16207; } .bar button.active { background: #fff; box-shadow: 0 4px 14px rgba(0,0,0,.08); }
8. Full Page — every tab pattern wired up in one complete, copy-pasteable file
Live preview
Complete HTML — copy this whole file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tabs Demo</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #1f2433; background: #f3f4f6; padding: 40px 20px; }
.wrap { max-width: 640px; margin: 0 auto; }
.wrap > h1 { font-size: 1.8rem; margin-bottom: 6px; }
.wrap > p.intro { color: #6b7280; margin-bottom: 28px; }
.card { background: #fff; border-radius: 14px; padding: 22px; box-shadow: 0 8px 30px rgba(0,0,0,.06); margin-bottom: 28px; }
.card h2 { font-size: 1rem; color: #9ca3af; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 14px; }
/* Shared panel */
.panel { padding: 18px; background: #f9fafb; border-radius: 10px; min-height: 90px; font-size: 14px; }
.panel h4 { margin-bottom: 6px; }
.panel[hidden] { display: none; }
/* Underline tabs */
.tabs .bar { display: flex; gap: 6px; border-bottom: 1px solid #eee; margin-bottom: 16px; }
.tabs .bar button { padding: 12px 16px; background: transparent; border: none; color: #666; cursor: pointer; font: inherit; font-size: 14px; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.tabs .bar button:hover { color: #111; }
.tabs .bar button.active { color: #ec4899; font-weight: 600; border-bottom-color: #ec4899; }
/* Pill tabs */
.pills .bar { display: inline-flex; gap: 4px; background: #f4f4f5; padding: 4px; border-radius: 999px; margin-bottom: 18px; }
.pills .bar button { padding: 8px 16px; border-radius: 999px; background: transparent; border: none; color: #555; cursor: pointer; font: inherit; font-size: 13px; font-weight: 600; }
.pills .bar button.active { background: #111; color: #fff; }
/* Vertical tabs */
.vertical { display: grid; grid-template-columns: 160px 1fr; gap: 20px; }
.vertical .bar { display: flex; flex-direction: column; gap: 4px; }
.vertical .bar button { text-align: left; padding: 10px 14px; border-radius: 8px; background: transparent; border: none; cursor: pointer; font: inherit; font-size: 14px; color: #555; }
.vertical .bar button:hover { background: #f4f4f5; }
.vertical .bar button.active { background: #ec4899; color: #fff; }
@media (max-width: 480px) {
.vertical { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="wrap">
<h1>Tabbed panels</h1>
<p class="intro">One small script powers every tab group below: click a tab, it becomes active and shows its matching panel.</p>
<div class="card tabs" data-tabs>
<h2>Underline</h2>
<div class="bar">
<button class="active" data-tab="o1">Overview</button>
<button data-tab="o2">Specs</button>
<button data-tab="o3">Reviews</button>
</div>
<div class="panel" id="o1"><h4>Overview</h4><p>A quick summary of the product and what it does.</p></div>
<div class="panel" id="o2" hidden><h4>Specs</h4><p>Dimensions, weight, materials, and technical details.</p></div>
<div class="panel" id="o3" hidden><h4>Reviews</h4><p>What customers are saying after buying.</p></div>
</div>
<div class="card pills" data-tabs>
<h2>Pills</h2>
<div class="bar">
<button class="active" data-tab="p1">All</button>
<button data-tab="p2">Popular</button>
<button data-tab="p3">Newest</button>
</div>
<div class="panel" id="p1"><p>Showing all items.</p></div>
<div class="panel" id="p2" hidden><p>Showing the most popular items.</p></div>
<div class="panel" id="p3" hidden><p>Showing the newest arrivals.</p></div>
</div>
<div class="card" data-tabs>
<h2>Vertical</h2>
<div class="vertical">
<div class="bar">
<button class="active" data-tab="s1">Profile</button>
<button data-tab="s2">Security</button>
<button data-tab="s3">Billing</button>
</div>
<div>
<div class="panel" id="s1"><h4>Profile</h4><p>Your public name, avatar, and bio.</p></div>
<div class="panel" id="s2" hidden><h4>Security</h4><p>Password and two-factor settings.</p></div>
<div class="panel" id="s3" hidden><h4>Billing</h4><p>Plan, invoices, and payment method.</p></div>
</div>
</div>
</div>
</div>
<script>
// One handler for every [data-tabs] group on the page.
document.querySelectorAll('[data-tabs]').forEach(group => {
const buttons = group.querySelectorAll('.bar button');
buttons.forEach(btn => {
btn.addEventListener('click', () => {
// highlight the clicked tab
buttons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// show the matching panel, hide the rest
buttons.forEach(b => {
const panel = group.querySelector('#' + b.dataset.tab);
if (panel) panel.hidden = (b !== btn);
});
});
});
});
</script>
</body>
</html>