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.