We use cookies for site analytics. Accept to help us understand how the site is used. See our Privacy Policy for details.
A medium frontend problem, graded against 7 test cases (4 of them hidden).
React and browser-platform problems - hooks, event handling, and rendering behaviour.
Reach for it when you see: A component, hook, or DOM-behaviour question rather than an algorithm.
More Frontendproblems →```tsx
export function Tabs({ items }: { items: { id: string; label: string; content: React.ReactNode }[] }) {
const [active, setActive] = useState(0);
const refs = useRef<HTMLButtonElement[]>([]);
function onKeyDown(e: React.KeyboardEvent) {
const next = tabKeyboardHandler(active, e.key, items.length);
if (next === -1) return;
e.preventDefault();
setActive(next);
refs.current[next]?.focus();
}
return (
<div>
<div role="tablist" onKeyDown={onKeyDown}>
{items.map((it, i) => (
<button
key={it.id}
ref={el => { if (el) refs.current[i] = el; }}
role="tab"
aria-selected={active === i}
aria-controls={`panel-${it.id}`}
id={`tab-${it.id}`}
tabIndex={active === i ? 0 : -1}
onClick={() => setActive(i)}
>
{it.label}
</button>
))}
</div>
{items.map((it, i) => (
<div
key={it.id}
role="tabpanel"
id={`panel-${it.id}`}
aria-labelledby={`tab-${it.id}`}
hidden={active !== i}
>
{it.content}
</div>
))}
</div>
);
}
```
Roving tabindex: only the active tab has `tabIndex={0}`; the rest are `-1`. This is the WAI-ARIA pattern: Tab moves *out of* the tablist, arrow keys move *within* it. Without roving tabindex, Tab walks through every trigger - bad UX in a tablist.
The full reference solution in every supported language stays in the editor above - reveal it there once you have had a real attempt.
Read off this problem's own test suite, so these are the cases a submission actually has to survive.
These apply to the pattern as a whole, not just this problem.