Skip to content

Instantly share code, notes, and snippets.

@olibooty
Created October 21, 2025 13:33
Show Gist options
  • Select an option

  • Save olibooty/d147544a72d31f594a15106784132a99 to your computer and use it in GitHub Desktop.

Select an option

Save olibooty/d147544a72d31f594a15106784132a99 to your computer and use it in GitHub Desktop.
import { useState } from 'react';
interface TabItem {
id: string;
title: React.ReactNode;
body: React.ReactNode;
}
interface TabPanelProps {
items: TabItem[];
}
export const TabPanel = (props: TabPanelProps) => {
const [activePanel, setActivePanel] = useState(props.items[0].id);
const [activeTab, setActiveTab] = useState(props.items[0].id);
return (
<div>
<div
role="tablist"
style={{
display: 'flex',
gap: '3px',
}}
>
{props.items.map((item, i) => {
const currentPanelSelected = activePanel === item.id
return (
<button
key={item.id}
id={item.id}
aria-controls={`${item.id}-panel`}
aria-selected={currentPanelSelected}
tabIndex={activeTab === item.id ? 0 : -1}
role="tab"
type="button"
onKeyUp={(evt) => {
if (evt.key === 'ArrowRight') {
const nextId = props.items[(i + 1) % props.items.length].id;
setActiveTab(nextId);
document.getElementById(nextId)?.focus();
}
if (evt.key === 'ArrowLeft') {
const nextId = props.items[(i - 1) % props.items.length].id;
setActiveTab(nextId);
document.getElementById(nextId)?.focus();
}
}}
onClick={() => {
setActivePanel(item.id);
}}
style={{
borderTopColor: currentPanelSelected ? 'red' : undefined,
borderTopWidth: currentPanelSelected ? '4px' : undefined
}}
>
{item.title}
</button>
);
})}
</div>
{props.items.map((item) => (
<div
key={item.id}
id={`${item.id}-panel`}
role="tabpanel"
aria-hidden={item.id !== activePanel}
aria-labelledby={item.id}
hidden={item.id !== activePanel}
>
{item.body}
</div>
))}
</div>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment