ComponentTabsNavigationInteractive
Tabs Component
A fully accessible tabs component with keyboard navigation. Supports horizontal layout with animated indicator.
Preview
This is the overview tab content. It provides a general summary.
Code
'use client'
import { useState, useRef, useEffect } from 'react'
import { clsx } from 'clsx'
interface Tab {
id: string
label: string
content: React.ReactNode
}
interface TabsProps {
tabs: Tab[]
defaultTab?: string
}
export function Tabs({ tabs, defaultTab }: TabsProps) {
const [activeTab, setActiveTab] = useState(defaultTab || tabs[0]?.id)
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 })
const tabsRef = useRef<Map<string, HTMLButtonElement>>(new Map())
useEffect(() => {
const activeButton = tabsRef.current.get(activeTab)
if (activeButton) {
setIndicatorStyle({
left: activeButton.offsetLeft,
width: activeButton.offsetWidth,
})
}
}, [activeTab])
const handleKeyDown = (e: React.KeyboardEvent, index: number) => {
const lastIndex = tabs.length - 1
if (e.key === 'ArrowRight') {
const nextIndex = index === lastIndex ? 0 : index + 1
setActiveTab(tabs[nextIndex].id)
} else if (e.key === 'ArrowLeft') {
const prevIndex = index === 0 ? lastIndex : index - 1
setActiveTab(tabs[prevIndex].id)
}
}
return (
<div>
<div className="relative border-b border-olive-200 dark:border-olive-700">
<div className="flex gap-1" role="tablist">
{tabs.map((tab, index) => (
<button
key={tab.id}
ref={(el) => el && tabsRef.current.set(tab.id, el)}
role="tab"
aria-selected={activeTab === tab.id}
tabIndex={activeTab === tab.id ? 0 : -1}
onClick={() => setActiveTab(tab.id)}
onKeyDown={(e) => handleKeyDown(e, index)}
className={clsx(
'px-4 py-2 text-sm font-medium transition-colors',
activeTab === tab.id
? 'text-olive-950 dark:text-white'
: 'text-olive-500 hover:text-olive-700 dark:hover:text-olive-300'
)}
>
{tab.label}
</button>
))}
</div>
<div
className="absolute bottom-0 h-0.5 bg-olive-950 transition-all duration-200 dark:bg-olive-300"
style={indicatorStyle}
/>
</div>
<div className="py-4" role="tabpanel">
{tabs.find((t) => t.id === activeTab)?.content}
</div>
</div>
)
}Looking for more?
Browse the full collection of components or check out other exploration topics.