Lab

Tabs Lab

Seven tab patterns — underline, pills, vertical, animated sliders, and card-style tabs.

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>