Tabs
Switchable tabbed content panels.
Code
components/Tabs.astro
---interface Tab { label: string;} interface Props { tabs: Tab[]; activeIndex?: number; class?: string;} const { tabs, activeIndex = 0, class: className = "",} = Astro.props;--- <div class={`tabs ${className} space-y-5`}> <div class="flex border-b border-black-onyx/10" role="tablist" aria-label="Content tabs"> {tabs.map((tab, i) => ( <button class={`tab-btn px-4 py-2 text-sm text-black-onyx/60 hover:text-black-onyx transition-colors ${i === activeIndex ? "tab-btn-active text-black-onyx font-medium border-b-2 border-black-onyx" : ""}`} role="tab" id={`tab-${i}`} aria-selected={i === activeIndex} aria-controls={`tabpanel-${i}`} tabindex={i === activeIndex ? 0 : -1} > {tab.label} </button> ))} </div> <slot /></div> <script> document.querySelectorAll(".tabs").forEach((tabsEl) => { const tabButtons = tabsEl.querySelectorAll('[role="tab"]'); const panels = tabsEl.querySelectorAll('[data-tab-panel]'); function activateTab(index) { tabButtons.forEach((btn, i) => { const isActive = i === index; btn.classList.toggle("tab-btn-active", isActive); btn.classList.toggle("text-black-onyx", isActive); btn.classList.toggle("font-medium", isActive); btn.classList.toggle("border-b-2", isActive); btn.classList.toggle("border-black-onyx", isActive); btn.setAttribute("aria-selected", isActive); btn.setAttribute("tabindex", isActive ? "0" : "-1"); btn.setAttribute("aria-controls", `tabpanel-${i}`); }); panels.forEach((panel, i) => { panel.id = `tabpanel-${i}`; panel.setAttribute("aria-labelledby", `tab-${i}`); panel.style.display = i === index ? "block" : "none"; }); } const activeBtn = tabsEl.querySelector(".tab-btn-active"); const initialIndex = activeBtn ? Array.from(tabButtons).indexOf(activeBtn) : 0; activateTab(initialIndex); tabButtons.forEach((btn, i) => { btn.addEventListener("click", () => activateTab(i)); }); tabsEl.addEventListener("keydown", (e) => { const current = Array.from(tabButtons).findIndex((btn) => btn.getAttribute("aria-selected") === "true"); let next = current; if (e.key === "ArrowRight") { next = (current + 1) % tabButtons.length; } else if (e.key === "ArrowLeft") { next = (current - 1 + tabButtons.length) % tabButtons.length; } else if (e.key === "Home") { next = 0; } else if (e.key === "End") { next = tabButtons.length - 1; } if (next !== current) { e.preventDefault(); activateTab(next); tabButtons[next]?.focus(); } }); });</script>
Preview
This is the preview panel with some sample content.
Details panel — additional information goes here.
Settings panel — configure your preferences.
Usage
<Tabs tabs={[{ label: "Preview" }, { label: "Details" }, { label: "Settings" }]}> <div data-tab-panel="0"> <p class="text-black-onyx/80">This is the preview panel with some sample content.</p> </div> <div data-tab-panel="1"> <p class="text-black-onyx/80">Details panel — additional information goes here.</p> </div> <div data-tab-panel="2"> <p class="text-black-onyx/80">Settings panel — configure your preferences.</p> </div></Tabs>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| tabs | Tab[] | — | Array of tabs with label. |
| activeIndex | number | 0 | Initially active tab index. |
| class | string | — | Additional CSS classes. |