Tempa UI

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.