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 6 test cases (3 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
type Mode = "single" | "multi";
type Item = { id: string; title: string; body: React.ReactNode };
export function Accordion({ items, mode = "single" }: { items: Item[]; mode?: Mode }) {
const [openIds, setOpenIds] = useState<Set<string>>(new Set());
const toggle = useCallback((id: string) => {
setOpenIds(prev => {
const next = new Set(prev);
if (mode === "single") {
if (next.has(id)) next.delete(id);
else { next.clear(); next.add(id); }
} else {
next.has(id) ? next.delete(id) : next.add(id);
}
return next;
});
}, [mode]);
return (
<div role="region">
{items.map(item => {
const isOpen = openIds.has(item.id);
return (
<div key={item.id}>
<button
aria-expanded={isOpen}
aria-controls={`panel-${item.id}`}
onClick={() => toggle(item.id)}
>
{item.title}
</button>
<div id={`panel-${item.id}`} role="region" hidden={!isOpen}>
{item.body}
</div>
</div>
);
})}
</div>
);
}
```
Accessibility notes:
- The trigger is a `<button>` (keyboard-focusable, Space/Enter activatable).
- `aria-expanded` reflects open state for screen readers.
- `aria-controls` links button to panel for AT navigation.
- Use `hidden` (or CSS `display:none`) - not just `visibility:hidden` - so collapsed content is removed from the tab order.
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.