tabs tablist navigation panels interactive tabs tablist tabpanel underline navigation panels tab navigation with panels switchable content tabs
Tabs Base
Fetch pattern JSON:
curl https://webspire.de/patterns/tabs/base.json base.html
<div class="ws-tabs mx-auto max-w-3xl px-6 py-12">
<div role="tablist" aria-label="Content tabs" class="flex border-b border-[var(--ws-tabs-border)]">
<button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1" class="border-b-2 border-[var(--ws-tabs-active-border)] px-4 py-2.5 text-sm font-semibold text-[var(--ws-tabs-active-text)] focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2" onclick="switchTab(this, 'panel-1')">Overview</button>
<button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" class="border-b-2 border-transparent px-4 py-2.5 text-sm font-medium text-[var(--ws-tabs-text)] hover:border-[var(--ws-tabs-border)] hover:text-[var(--ws-tabs-active-text)] focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2" onclick="switchTab(this, 'panel-2')">Features</button>
<button role="tab" id="tab-3" aria-selected="false" aria-controls="panel-3" class="border-b-2 border-transparent px-4 py-2.5 text-sm font-medium text-[var(--ws-tabs-text)] hover:border-[var(--ws-tabs-border)] hover:text-[var(--ws-tabs-active-text)] focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2" onclick="switchTab(this, 'panel-3')">Pricing</button>
</div>
<div id="panel-1" role="tabpanel" aria-labelledby="tab-1" class="py-6">
<h3 class="text-lg font-semibold text-[var(--ws-tabs-active-text)]">Overview</h3>
<p class="mt-2 text-[var(--ws-tabs-text)]">This is the overview panel. It provides a high-level summary of the product, its purpose, and the value it delivers to users.</p>
</div>
<div id="panel-2" role="tabpanel" aria-labelledby="tab-2" class="hidden py-6">
<h3 class="text-lg font-semibold text-[var(--ws-tabs-active-text)]">Features</h3>
<p class="mt-2 text-[var(--ws-tabs-text)]">Explore the full feature set including responsive design, dark mode support, accessibility compliance, and copy-paste simplicity.</p>
</div>
<div id="panel-3" role="tabpanel" aria-labelledby="tab-3" class="hidden py-6">
<h3 class="text-lg font-semibold text-[var(--ws-tabs-active-text)]">Pricing</h3>
<p class="mt-2 text-[var(--ws-tabs-text)]">All patterns are free and open source. Use them in personal and commercial projects without any restrictions.</p>
</div>
<script>
function switchTab(selectedTab, panelId) {
const tablist = selectedTab.closest('[role="tablist"]');
const container = tablist.parentElement;
tablist.querySelectorAll('[role="tab"]').forEach(function(tab) {
tab.setAttribute('aria-selected', 'false');
tab.className = 'border-b-2 border-transparent px-4 py-2.5 text-sm font-medium text-[var(--ws-tabs-text)] hover:border-[var(--ws-tabs-border)] hover:text-[var(--ws-tabs-active-text)] focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2';
});
selectedTab.setAttribute('aria-selected', 'true');
selectedTab.className = 'border-b-2 border-[var(--ws-tabs-active-border)] px-4 py-2.5 text-sm font-semibold text-[var(--ws-tabs-active-text)] focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2';
container.querySelectorAll('[role="tabpanel"]').forEach(function(panel) {
panel.classList.add('hidden');
});
container.querySelector('#' + panelId).classList.remove('hidden');
}
</script>
</div>
Details
Responsive Dark Mode Tailwind Only SSR Safe Copy & Paste Requires JS
Stable Published
tabstablistnavigationpanelsinteractive
Slots
| Name | Required | Description |
|---|---|---|
| tabs | Yes | Tab buttons with role="tab" and aria-controls. |
| panels | Yes | Tab panels with role="tabpanel" and aria-labelledby. |
Underline-style tab component with three panels and accessible ARIA roles. Uses minimal inline JavaScript for switching and a border-bottom active indicator.