Introduction
Accordion menus are one of the most common UI patterns in modern web development. From FAQ sections to product feature breakdowns, accordions help organize content while saving valuable screen space. In this comprehensive guide, you'll learn how to build a fully functional, accessible, and animatable accordion menu using React from scratch.
We'll cover everything from basic implementation with React hooks to advanced features like smooth animations and accessibility compliance. Whether you're building a simple FAQ section or a complex navigation menu, this guide will give you the foundation you need. Building an accordion from scratch also teaches fundamental React concepts that apply across all your projects, including state management with hooks, component composition, event handling, and accessibility patterns that appear throughout React development.
Key concepts covered in this guide
React Hooks Fundamentals
Master useState for managing accordion open/close state with clean, predictable code.
Component Architecture
Build reusable Accordion and AccordionItem components that integrate seamlessly into any project.
CSS Styling & Animations
Create smooth transitions and polished visuals with CSS transitions and responsive design.
Accessibility Compliance
Implement WCAG-compliant accordions with ARIA attributes, keyboard navigation, and screen reader support.
Advanced State Patterns
Handle single vs multiple open panels with controlled and uncontrolled component patterns.
Performance Optimization
Memoization techniques and best practices for optimal rendering performance.
Understanding Accordion Components in React
An accordion is a vertically stacked set of interactive headers that expand or collapse to reveal associated content. The term comes from the musical instrument that expands and contracts, and the UI pattern works similarly--panels expand to show content and collapse to hide it.
What Makes a Good Accordion
A well-designed accordion component should have several key characteristics that distinguish it from simple collapsible sections. First, it needs clear visual indicators that communicate interactivity--users should immediately understand they can click to expand or collapse content. Second, it should provide smooth, performant animations that don't feel jarring when content appears or disappears. Third, it must be fully accessible, meaning keyboard users and screen reader users can navigate and interact with the component without barriers.
Common Use Cases for Accordions
Accordions appear throughout web applications for several distinct purposes. FAQ sections represent the most common use case, where questions serve as headers and answers expand to show detailed responses. Product specification pages often use accordions to organize technical details without overwhelming users with information. Navigation menus, particularly on mobile devices, frequently employ accordion patterns to organize hierarchical links while keeping the interface clean. Beyond these common examples, accordions work well for organizing form fields into logical groups, displaying terms and conditions or privacy policies, breaking down long articles into digestible sections, and presenting comparison tables in a space-efficient manner.
Why Build from Scratch
While numerous React libraries provide pre-built accordion components, building from scratch offers significant advantages for your development skills and your applications. Understanding the underlying mechanics helps you debug issues more effectively when problems arise. You gain complete control over the component's behavior, styling, and accessibility features without being constrained by library opinions. The implementation can be exactly tailored to your specific needs rather than working around library limitations.
1import { useState } from 'react';2import './AccordionItem.css';3 4function AccordionItem({ title, content, isOpen, onToggle }) {5 return (6 <div className={`accordion-item ${isOpen ? 'open' : ''}`}>7 <button8 className="accordion-header"9 onClick={onToggle}10 aria-expanded={isOpen}11 >12 <span className="accordion-title">{title}</span>13 <span className="accordion-icon" aria-hidden="true">14 {isOpen ? '−' : '+'}15 </span>16 </button>17 {isOpen && (18 <div className="accordion-content">19 <p>{content}</p>20 </div>21 )}22 </div>23 );24}25 26export default AccordionItem;1import { useState } from 'react';2import AccordionItem from './AccordionItem';3import './Accordion.css';4 5function Accordion({ items }) {6 const [openIndex, setOpenIndex] = useState(null);7 8 const handleToggle = (index) => {9 setOpenIndex(openIndex === index ? null : index);10 };11 12 return (13 <div className="accordion">14 {items.map((item, index) => (15 <AccordionItem16 key={index}17 title={item.title}18 content={item.content}19 isOpen={openIndex === index}20 onToggle={() => handleToggle(index)}21 />22 ))}23 </div>24 );25}26 27export default Accordion;1.accordion {2 width: 100%;3 max-width: 600px;4 margin: 0 auto;5 border: 1px solid #e0e0e0;6 border-radius: 8px;7 overflow: hidden;8}9 10.accordion-header {11 width: 100%;12 display: flex;13 justify-content: space-between;14 align-items: center;15 padding: 16px 20px;16 background: #ffffff;17 border: none;18 border-bottom: 1px solid #e0e0e0;19 cursor: pointer;20 font-size: 16px;21 font-weight: 500;22 text-align: left;23 color: #333;24 transition: background-color 0.2s ease;25}26 27.accordion-header:hover {28 background-color: #f5f5f5;29}30 31.accordion-header:focus {32 outline: 2px solid #007bff;33 outline-offset: -2px;34}35 36.accordion-icon {37 font-size: 20px;38 font-weight: 300;39 color: #666;40 transition: transform 0.2s ease;41}42 43.accordion-item.open .accordion-icon {44 transform: rotate(180deg);45}46 47.accordion-content {48 padding: 20px;49 background-color: #fafafa;50 border-bottom: 1px solid #e0e0e0;51 line-height: 1.6;52 color: #555;53}Implementing Smooth Animations
Animations enhance the user experience by providing visual continuity when panels open and close. CSS transitions offer the simplest approach to adding animation without complex JavaScript calculations.
CSS Transition Approach
The CSS transition method animates properties when panels open and close.
.accordion-content {
padding: 20px;
background-color: #fafafa;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.accordion-item.open .accordion-content {
max-height: 500px;
}
This technique uses max-height to animate the reveal. The element animates from max-height: 0 (implicit) to the specified maximum height. The transition creates the smooth expansion effect.
Enhanced Animation with CSS Variables
For more flexible animations that work regardless of content height, use JavaScript to calculate the exact content height and apply it dynamically.
.accordion-content-wrapper {
overflow: hidden;
transition: all 0.3s ease;
}
.accordion-content-wrapper.collapsing {
height: 0 !important;
}
The CSS-only approach works well for many use cases, but if your content varies significantly in length, you might need JavaScript-based height calculations. As noted in FreeCodeCamp's guide to accordion menus, libraries like react-transition-group or CSS-in-JS solutions can also help manage these animations more elegantly.
Managing Multiple Open Panels
Some accordion implementations allow multiple panels to be open simultaneously, while others restrict expansion to a single panel at a time. The appropriate choice depends on your use case and user expectations.
Implementing Multiple Open Panels
To support multiple open panels, change the state structure to track an array of open indices rather than a single value:
function Accordion({ items, allowMultiple = false }) {
const [openIndices, setOpenIndices] = useState([]);
const handleToggle = (index) => {
if (allowMultiple) {
setOpenIndices(prev =>
prev.includes(index)
? prev.filter(i => i !== index)
: [...prev, index]
);
} else {
setOpenIndices(prev =>
prev.includes(index) ? [] : [index]
);
}
};
return (
<div className="accordion">
{items.map((item, index) => (
<AccordionItem
key={index}
title={item.title}
content={item.content}
isOpen={openIndices.includes(index)}
onToggle={() => handleToggle(index)}
/>
))}
</div>
);
}
The allowMultiple prop provides flexibility--use true for contexts where users benefit from comparing multiple sections, and false when you want to focus attention on a single section at a time. According to GeeksforGeeks's React accordion tutorial, this approach enables flexible data handling through props while maintaining clean component architecture.
When to Allow Multiple Panels
Consider allowing multiple open panels when users need to compare information across sections, such as comparing product features or pricing tiers. For FAQ sections where users often have multiple questions, allowing multiple open panels improves the user experience by eliminating the need to repeatedly open and close panels.
Ensuring Accessibility
Accessibility isn't optional--it's essential for creating inclusive web experiences. Accordions present specific accessibility challenges that require careful attention to ensure all users can interact with the component effectively.
Keyboard Navigation
Users should be able to navigate through accordion headers using keyboard arrow keys. The Tab key moves focus between interactive elements, and Enter or Space should activate accordion headers. Arrow keys within the accordion can provide additional navigation patterns. Implementing proper keyboard support is a fundamental aspect of accessible web development that ensures your components work for everyone.
Key ARIA Attributes
aria-expanded- communicates current state to assistive technologiesaria-controls- links the header to its content panelrole="region"- identifies the content area as significanthidden- ensures hidden content is properly removed from the accessibility tree
Screen Reader Considerations
Screen reader users need clear, descriptive content to understand the accordion's purpose and current state. Each header should have a meaningful label that describes what will expand, and the content should be structured logically once revealed. Announcements should inform users when panels open and close--some implementations use aria-live regions to announce these state changes.
1function AccordionItem({ title, content, isOpen, onToggle }) {2 const handleKeyDown = (e) => {3 if (e.key === 'Enter' || e.key === ' ') {4 e.preventDefault();5 onToggle();6 }7 };8 9 return (10 <div className="accordion-item">11 <button12 className="accordion-header"13 onClick={onToggle}14 onKeyDown={handleKeyDown}15 aria-expanded={isOpen}16 aria-controls={`accordion-content-${title.toLowerCase().replace(/\s+/g, '-')}`}17 >18 <span className="accordion-title">{title}</span>19 </button>20 <div21 id={`accordion-content-${title.toLowerCase().replace(/\s+/g, '-')}`}22 role="region"23 aria-labelledby={`accordion-header-${title.toLowerCase().replace(/\s+/g, '-')}`}24 className="accordion-content"25 hidden={!isOpen}26 >27 <p>{content}</p>28 </div>29 </div>30 );31}Advanced Patterns and Best Practices
Controlled vs Uncontrolled Components
React components can be either controlled or uncontrolled:
Controlled components have state managed entirely by their parent:
function Accordion({ items, openIndices, onToggle }) {
return (
<div className="accordion">
{items.map((item, index) => (
<AccordionItem
key={index}
title={item.title}
content={item.content}
isOpen={openIndices.includes(index)}
onToggle={() => onToggle(index)}
/>
))}
</div>
);
}
Controlled components give the parent complete control over state, making them easier to integrate with forms, data from APIs, or complex application state. Uncontrolled components manage their own internal state and are simpler to use in isolation but offer less flexibility for complex integrations.
Performance Optimization
For accordions with many items or complex content, performance optimization becomes important. Consider memoizing components to prevent unnecessary re-renders and lazy-loading content that's not immediately visible. The React documentation recommends using React.memo for preventing unnecessary re-renders and useMemo for caching expensive computations.
Integration with Design Systems
When building accordions for production applications, consider how the component fits into your broader design system. Define consistent props for:
- Size variants: small, medium, large
- Appearance: borders, shadows, background colors
- Behavior: animation duration, easing functions
This approach allows the accordion to adapt to different design contexts without code changes, making it more reusable across your application and maintaining consistency in your custom web development projects.
1import { memo, useMemo } from 'react';2 3const AccordionItem = memo(function AccordionItem({ title, content, isOpen, onToggle }) {4 return (5 <div className="accordion-item">6 <button7 className="accordion-header"8 onClick={onToggle}9 aria-expanded={isOpen}10 >11 <span className="accordion-title">{title}</span>12 </button>13 {isOpen && (14 <div className="accordion-content">15 <p>{content}</p>16 </div>17 )}18 </div>19 );20});21 22function Accordion({ items }) {23 const processedItems = useMemo(() =>24 items.map(item => ({25 ...item,26 contentId: item.title.toLowerCase().replace(/\s+/g, '-')27 })),28 [items]29 );30 31 return (32 <div className="accordion">33 {processedItems.map((item, index) => (34 <AccordionItem35 key={item.contentId}36 title={item.title}37 content={item.content}38 isOpen={item.isOpen}39 onToggle={item.onToggle}40 />41 ))}42 </div>43 );44}Frequently Asked Questions
Conclusion
Building an accordion component from scratch teaches valuable React skills while giving you complete control over functionality and appearance. The implementation we've explored covers the essential patterns: state management with hooks (useState), component composition (Accordion + AccordionItem), accessibility features (ARIA attributes, keyboard navigation), and styling approaches (CSS transitions, responsive design).
Start with the basic implementation and gradually add features as needed:
- Begin with functional state management
- Add styling and animations
- Implement accessibility features
- Optimize for performance
- Adapt to your design system
The accessibility work might seem like extra effort, but it ensures your components work for everyone. The animation polish might be skipped for simple use cases but adds significant polish for user-facing features.
Remember that the best accordion is one that serves your specific use case. The patterns in this guide provide a foundation--adapt them to fit your project's requirements, design system, and user expectations. For professional assistance with React component development or custom web application development, contact our team to discuss how we can help bring your projects to life.
Sources
- FreeCodeCamp - How to Build Simpler Accordion Menus with HTML details - Comprehensive guide covering native HTML accordion elements, CSS styling techniques, and accessibility considerations.
- GeeksforGeeks - Accordion Template using ReactJS and Tailwind - Complete React implementation demonstrating state management, component structure, and dynamic data handling.