Loading challenge...
Preparing the challenge details...
Loading challenge...
Preparing the challenge details...
Learn to build an accessible accordion component in React with smooth animations, keyboard navigation, and single or multi-expand modes. Perfect for FAQs and collapsible UI sections.
Build an accordion component that allows users to expand and collapse content sections. The key challenges are managing state for multiple accordion items, implementing smooth animations, ensuring accessibility with keyboard navigation, and handling edge cases like preventing multiple items from being open simultaneously (if needed).
An accordion is a UI component that displays a list of items where each item can be expanded to reveal its content or collapsed to hide it. This pattern is commonly used for FAQs, navigation menus, and organizing content into collapsible sections. According to the W3C WAI-ARIA Authoring Practices, an accordion is a vertically stacked set of interactive headings that each contain a title representing a section of content.
The implementation uses a controlled component pattern with centralized state management:
┌─────────────────────────────────────┐
│ AccordionDemo (Parent) │
│ - Provides data array │
│ - Configures singleOpen mode │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ AccordionComponent │
│ - Manages openItems state │
│ - Handles keyboard navigation │
│ - Manages button refs │
│ - Renders accordion items │
└─────────────────────────────────────┘
Key Components:
The primary challenge is managing which accordion items are open or closed, especially when dealing with multiple items and different behaviors (single vs. multiple items open at once). We'll use React's useState hook for this purpose.
The component uses an array to track open items:
TSXcomponent.tsx1const [openItems, setOpenItems] = useState<number[]>([]);
Why an array?
openItems.includes(item.id)The component supports two modes controlled by the singleOpen prop:
Single Open Mode (Default):
TSXcomponent.tsx1if (singleOpen) { 2 return prev[0] === id ? [] : [id]; 3}
Multiple Open Mode:
TSXcomponent.tsx1else { 2 return prev.includes(id) 3 ? prev.filter(openId => openId !== id) 4 : [...prev, id]; 5}
TSXcomponent.tsx1const handleOpen = (id: number) => { 2 setOpenItems(prev => { 3 if (singleOpen) { 4 // Single mode: replace array with new item or empty 5 return prev[0] === id ? [] : [id]; 6 } else { 7 // Multiple mode: add or remove from array 8 return prev.includes(id) 9 ? prev.filter(openId => openId !== id) 10 : [...prev, id]; 11 } 12 }); 13}
Key Logic:
[]). Otherwise, open only that item ([id]).The component implements full keyboard navigation following ARIA best practices:
TSXcomponent.tsx1const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>, id: number, index: number) => { 2 const key = e.key; 3 const keyCode = e.keyCode || e.which; 4 5 // Enter or Space: Toggle the panel 6 if (key === 'Enter' || key === ' ' || keyCode === 13 || keyCode === 32) { 7 e.preventDefault(); 8 handleOpen(id); 9 return; 10 } 11 12 // Arrow keys: Navigate between headers 13 if (key === 'ArrowDown' || keyCode === 40) { 14 e.preventDefault(); 15 const nextIndex = index < data.length - 1 ? index + 1 : 0; 16 const nextButton = buttonRefs.current[data[nextIndex].id]; 17 nextButton?.focus(); 18 return; 19 } 20 21 // ... similar for ArrowUp, Home, End 22}
The component uses a refs map to store references to all button elements. Learn more about useRef in the React documentation:
TSXcomponent.tsx1const buttonRefs = useRef<{ [key: number]: HTMLButtonElement | null }>({}); 2 3// Store ref when rendering 4<button 5 ref={(el) => { buttonRefs.current[item.id] = el; }} 6 // ... 7>
Why use refs?
focus() callsArrow keys implement circular navigation:
This creates a seamless keyboard experience where users can cycle through all items.
The component implements comprehensive ARIA attributes and semantic HTML:
TSXcomponent.tsx1<button 2 id={buttonId} 3 aria-expanded={isOpen} 4 aria-controls={panelId} 5 // ... 6>
aria-expanded: Indicates whether the panel is open (true) or closed (false)aria-controls: Links the button to the panel it controlsid: Unique identifier for the buttonTSXcomponent.tsx1<div 2 id={panelId} 3 role="region" 4 aria-labelledby={buttonId} 5 hidden={!isOpen} 6 // ... 7>
role="region": Identifies the content areaaria-labelledby: Links the panel to its controlling buttonhidden: Hides the panel from screen readers when closed<button> for clickable headers (not <div>)TSXcomponent.tsx1interface AccordionItem { 2 id: number; 3 title: string; 4 content: string; 5} 6 7interface AccordionComponentProps { 8 data: AccordionItem[]; 9 singleOpen?: boolean; 10}
TSXcomponent.tsx1{data.map((item, index) => { 2 const buttonId = `accordion-button-${item.id}`; 3 const panelId = `accordion-panel-${item.id}`; 4 const isOpen = openItems.includes(item.id); 5 6 return ( 7 <div key={item.id} className='accordion-item'> 8 <button 9 ref={(el) => { buttonRefs.current[item.id] = el; }} 10 id={buttonId} 11 onClick={() => handleOpen(item.id)} 12 onKeyDown={(e) => handleKeyDown(e, item.id, index)} 13 aria-expanded={isOpen} 14 aria-controls={panelId} 15 > 16 {item.title} 17 <ChevronDownIcon className={isOpen ? 'open' : ''} /> 18 </button> 19 <div 20 id={panelId} 21 role="region" 22 aria-labelledby={buttonId} 23 hidden={!isOpen} 24 > 25 <p>{item.content}</p> 26 </div> 27 </div> 28 ); 29})}
Key Points:
isOpen calculated from state arrayThe chevron icon rotates when an item is opened:
CSSstyles.css1.accordion-item-header-icon { 2 transition: transform 0.3s ease; 3} 4 5.accordion-item-header-icon-open { 6 transform: rotate(180deg); 7}
This provides clear visual feedback about the accordion's state.
TSXcomponent.tsx1const data = [ 2 { id: 1, title: 'Section 1', content: 'Content 1' }, 3 { id: 2, title: 'Section 2', content: 'Content 2' }, 4 { id: 3, title: 'Section 3', content: 'Content 3' }, 5]; 6 7// Single open mode (default) 8<AccordionComponent data={data} /> 9 10// Multiple open mode 11<AccordionComponent data={data} singleOpen={false} />
prev => ...) ensures correct state batchinghidden instead of conditional rendering keeps DOM structure stableGoal: Implement an accordion component that can be used to display a list of items.
Continue learning with these related challenges

Create an interactive image carousel in React with smooth slide transitions, navigation arrows, dot indicators, autoplay, and touch/swipe support for mobile devices.

Build a dynamic Tic Tac Toe game in React with customizable grid sizes, win detection algorithms, player turn management, and game reset functionality. Great for interviews.

Create an animated dice roller component in React with realistic rolling animations, random number generation, multiple dice support, and roll history tracking.

Create an interactive image carousel in React with smooth slide transitions, navigation arrows, dot indicators, autoplay, and touch/swipe support for mobile devices.

Build a dynamic Tic Tac Toe game in React with customizable grid sizes, win detection algorithms, player turn management, and game reset functionality. Great for interviews.

Create an animated dice roller component in React with realistic rolling animations, random number generation, multiple dice support, and roll history tracking.