features tabs preview saas platform governance interactive features tabs tab-switcher preview platform saas governance tab switcher feature section interactive feature preview with tabs sidebar nav with content panel
Features Tab Switcher
Fetch pattern JSON:
curl https://webspire.de/patterns/features/tab-switcher.json tab-switcher.html
<section class="ws-features bg-[var(--ws-features-bg)] py-20">
<div class="mx-auto max-w-7xl px-6">
<div class="mx-auto max-w-2xl text-center">
<p class="text-sm font-semibold uppercase tracking-[0.18em] text-[var(--ws-features-accent)]">Platform</p>
<h2 class="mt-3 text-balance text-3xl font-bold tracking-tight text-[var(--ws-features-text)] sm:text-4xl">Everything you need to stay in control</h2>
<p class="mt-4 text-lg text-[var(--ws-features-text-soft)]">Powerful governance tools built for teams that move fast without breaking things.</p>
</div>
<div class="mt-16 grid gap-8 lg:grid-cols-[320px_1fr]">
<!-- Tab list -->
<div class="flex flex-col gap-2" role="tablist" aria-label="Feature tabs">
<button
role="tab"
aria-selected="true"
aria-controls="tab-panel-1"
id="tab-1"
data-tab="1"
class="ws-tab-btn group flex items-start gap-4 rounded-xl border border-[var(--ws-features-border)] bg-[var(--ws-features-card-bg)] p-4 text-left transition-all hover:border-[var(--ws-features-accent)] data-[active]:border-[var(--ws-features-accent)] data-[active]:shadow-sm"
>
<span class="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[var(--ws-features-accent)]/10 text-[var(--ws-features-accent)]">
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
</span>
<div>
<p class="font-semibold text-[var(--ws-features-text)]">Audit Logs</p>
<p class="mt-1 text-sm text-[var(--ws-features-text-soft)]">Complete history of every access and change across your organization.</p>
</div>
</button>
<button
role="tab"
aria-selected="false"
aria-controls="tab-panel-2"
id="tab-2"
data-tab="2"
class="ws-tab-btn group flex items-start gap-4 rounded-xl border border-[var(--ws-features-border)] bg-[var(--ws-features-card-bg)] p-4 text-left transition-all hover:border-[var(--ws-features-accent)] data-[active]:border-[var(--ws-features-accent)] data-[active]:shadow-sm"
>
<span class="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[var(--ws-features-accent)]/10 text-[var(--ws-features-accent)]">
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
</span>
<div>
<p class="font-semibold text-[var(--ws-features-text)]">Access Controls</p>
<p class="mt-1 text-sm text-[var(--ws-features-text-soft)]">Fine-grained roles and permissions for every resource and environment.</p>
</div>
</button>
<button
role="tab"
aria-selected="false"
aria-controls="tab-panel-3"
id="tab-3"
data-tab="3"
class="ws-tab-btn group flex items-start gap-4 rounded-xl border border-[var(--ws-features-border)] bg-[var(--ws-features-card-bg)] p-4 text-left transition-all hover:border-[var(--ws-features-accent)] data-[active]:border-[var(--ws-features-accent)] data-[active]:shadow-sm"
>
<span class="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[var(--ws-features-accent)]/10 text-[var(--ws-features-accent)]">
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</span>
<div>
<p class="font-semibold text-[var(--ws-features-text)]">Approval Workflows</p>
<p class="mt-1 text-sm text-[var(--ws-features-text-soft)]">Require sign-off before sensitive changes go live in production.</p>
</div>
</button>
<button
role="tab"
aria-selected="false"
aria-controls="tab-panel-4"
id="tab-4"
data-tab="4"
class="ws-tab-btn group flex items-start gap-4 rounded-xl border border-[var(--ws-features-border)] bg-[var(--ws-features-card-bg)] p-4 text-left transition-all hover:border-[var(--ws-features-accent)] data-[active]:border-[var(--ws-features-accent)] data-[active]:shadow-sm"
>
<span class="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[var(--ws-features-accent)]/10 text-[var(--ws-features-accent)]">
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</span>
<div>
<p class="font-semibold text-[var(--ws-features-text)]">Temporary Access</p>
<p class="mt-1 text-sm text-[var(--ws-features-text-soft)]">Grant time-limited access that expires automatically. No cleanup needed.</p>
</div>
</button>
</div>
<!-- Preview panels -->
<div class="relative overflow-hidden rounded-2xl border border-[var(--ws-features-border)] bg-[var(--ws-features-card-bg)] min-h-[380px]">
<div id="tab-panel-1" role="tabpanel" aria-labelledby="tab-1" class="ws-tab-panel absolute inset-0 flex flex-col p-8 transition-opacity duration-200">
<div class="mb-4 flex items-center gap-2">
<span class="h-3 w-3 rounded-full bg-red-400"></span>
<span class="h-3 w-3 rounded-full bg-amber-400"></span>
<span class="h-3 w-3 rounded-full bg-green-400"></span>
</div>
<div class="flex-1 rounded-xl bg-[var(--ws-features-bg)] p-6 font-mono text-sm">
<p class="text-[var(--ws-features-text-soft)]">audit_log</p>
<div class="mt-4 space-y-2">
<div class="flex gap-4 text-xs"><span class="text-[var(--ws-features-text-soft)]">2026-03-29 14:23</span><span class="text-emerald-500">READ</span><span class="text-[var(--ws-features-text)]">prod/DATABASE_URL</span><span class="text-[var(--ws-features-text-soft)]">alice@company.com</span></div>
<div class="flex gap-4 text-xs"><span class="text-[var(--ws-features-text-soft)]">2026-03-29 14:19</span><span class="text-amber-500">UPDATE</span><span class="text-[var(--ws-features-text)]">prod/API_KEY</span><span class="text-[var(--ws-features-text-soft)]">bob@company.com</span></div>
<div class="flex gap-4 text-xs"><span class="text-[var(--ws-features-text-soft)]">2026-03-29 13:55</span><span class="text-red-500">DELETE</span><span class="text-[var(--ws-features-text)]">staging/OLD_TOKEN</span><span class="text-[var(--ws-features-text-soft)]">ci-bot</span></div>
<div class="flex gap-4 text-xs"><span class="text-[var(--ws-features-text-soft)]">2026-03-29 13:42</span><span class="text-emerald-500">CREATE</span><span class="text-[var(--ws-features-text)]">dev/STRIPE_KEY</span><span class="text-[var(--ws-features-text-soft)]">alice@company.com</span></div>
</div>
</div>
</div>
<div id="tab-panel-2" role="tabpanel" aria-labelledby="tab-2" class="ws-tab-panel absolute inset-0 hidden flex-col p-8">
<h3 class="font-semibold text-[var(--ws-features-text)]">Role assignments</h3>
<div class="mt-6 space-y-3">
<div class="flex items-center justify-between rounded-lg border border-[var(--ws-features-border)] px-4 py-3">
<div class="flex items-center gap-3"><div class="h-8 w-8 rounded-full bg-gradient-to-br from-violet-400 to-purple-500"></div><div><p class="text-sm font-medium text-[var(--ws-features-text)]">alice@company.com</p><p class="text-xs text-[var(--ws-features-text-soft)]">Engineering</p></div></div>
<span class="rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-medium text-emerald-700">Admin</span>
</div>
<div class="flex items-center justify-between rounded-lg border border-[var(--ws-features-border)] px-4 py-3">
<div class="flex items-center gap-3"><div class="h-8 w-8 rounded-full bg-gradient-to-br from-sky-400 to-blue-500"></div><div><p class="text-sm font-medium text-[var(--ws-features-text)]">bob@company.com</p><p class="text-xs text-[var(--ws-features-text-soft)]">DevOps</p></div></div>
<span class="rounded-full bg-sky-100 px-2.5 py-0.5 text-xs font-medium text-sky-700">Developer</span>
</div>
<div class="flex items-center justify-between rounded-lg border border-[var(--ws-features-border)] px-4 py-3">
<div class="flex items-center gap-3"><div class="h-8 w-8 rounded-full bg-gradient-to-br from-amber-400 to-orange-500"></div><div><p class="text-sm font-medium text-[var(--ws-features-text)]">ci-bot</p><p class="text-xs text-[var(--ws-features-text-soft)]">Automation</p></div></div>
<span class="rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-medium text-amber-700">Read-only</span>
</div>
</div>
</div>
<div id="tab-panel-3" role="tabpanel" aria-labelledby="tab-3" class="ws-tab-panel absolute inset-0 hidden flex-col p-8">
<h3 class="font-semibold text-[var(--ws-features-text)]">Pending approvals</h3>
<div class="mt-6 space-y-3">
<div class="rounded-lg border border-amber-200 bg-amber-50/50 px-4 py-4">
<div class="flex items-start justify-between gap-4">
<div><p class="text-sm font-medium text-[var(--ws-features-text)]">Update STRIPE_SECRET_KEY</p><p class="mt-0.5 text-xs text-[var(--ws-features-text-soft)]">Requested by alice · production environment</p></div>
<div class="flex gap-2"><button class="rounded-md bg-emerald-600 px-3 py-1 text-xs font-medium text-white">Approve</button><button class="rounded-md border border-[var(--ws-features-border)] px-3 py-1 text-xs font-medium text-[var(--ws-features-text)]">Deny</button></div>
</div>
</div>
<div class="rounded-lg border border-[var(--ws-features-border)] px-4 py-4 opacity-60">
<p class="text-sm font-medium text-[var(--ws-features-text)]">Delete legacy API tokens</p>
<p class="mt-0.5 text-xs text-[var(--ws-features-text-soft)]">Approved 2h ago · by admin</p>
</div>
</div>
</div>
<div id="tab-panel-4" role="tabpanel" aria-labelledby="tab-4" class="ws-tab-panel absolute inset-0 hidden flex-col p-8">
<h3 class="font-semibold text-[var(--ws-features-text)]">Temporary access grants</h3>
<div class="mt-6 space-y-3">
<div class="rounded-lg border border-[var(--ws-features-border)] px-4 py-3">
<div class="flex items-center justify-between"><p class="text-sm font-medium text-[var(--ws-features-text)]">contractor@agency.com</p><span class="text-xs text-emerald-600 font-medium">Active · expires in 2h</span></div>
<div class="mt-2 h-1.5 w-full rounded-full bg-[var(--ws-features-bg)]"><div class="h-1.5 w-[30%] rounded-full bg-emerald-500"></div></div>
<p class="mt-1.5 text-xs text-[var(--ws-features-text-soft)]">Read access · staging environment</p>
</div>
<div class="rounded-lg border border-[var(--ws-features-border)] px-4 py-3 opacity-50">
<div class="flex items-center justify-between"><p class="text-sm font-medium text-[var(--ws-features-text)]">auditor@firm.com</p><span class="text-xs text-[var(--ws-features-text-soft)]">Expired</span></div>
<p class="mt-1 text-xs text-[var(--ws-features-text-soft)]">Read access · 24h · all environments</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const tabs = document.querySelectorAll('.ws-tab-btn');
const panels = document.querySelectorAll('.ws-tab-panel');
tabs.forEach((tab) => {
tab.addEventListener('click', () => {
const target = tab.dataset.tab;
tabs.forEach((t) => { t.setAttribute('aria-selected', 'false'); t.removeAttribute('data-active'); });
panels.forEach((p) => p.classList.add('hidden'));
tab.setAttribute('aria-selected', 'true');
tab.setAttribute('data-active', '');
const panel = document.getElementById('tab-panel-' + target);
if (panel) panel.classList.remove('hidden');
});
});
</script>
</section>
Details
Responsive Dark Mode Tailwind Only Copy & Paste Requires JS
Stable Published
featurestabspreviewsaasplatformgovernanceinteractive
Slots
| Name | Required | Description |
|---|---|---|
| heading | No | Section title and subtitle. |
| tabs | Yes | Vertical tab buttons with icon, title, and description. |
| panels | Yes | Preview panels revealed per tab — can be UI mockups, code snippets, or visuals. |
Vertical tab navigation paired with a live preview panel. Clicking a tab reveals its associated content — ideal for showing multiple platform features without overwhelming the page. Uses minimal vanilla JS for tab switching; no dependencies required.