Frontend Machine Coding: Build a Dynamic Accordion Component
Learn how to build a flexible accordion component with smooth animations, accessibility features, and configurable behavior using React
Frontend Machine Coding: Build a Dynamic Accordion Component
Problem Statement
Build a reusable accordion component that can handle multiple collapsible sections with smooth animations, accessibility features, and configurable behavior. This is a common UI pattern used in FAQs, navigation menus, and content organization.
Requirements
Must-Have Features:
- Multiple accordion sections that can expand/collapse
- Smooth animations for opening and closing
- Only one section open at a time (accordion behavior)
- Keyboard navigation support (Arrow keys, Enter, Space)
- Accessible with proper ARIA attributes
- Configurable content for each section
- Responsive design
Nice-to-Have Features:
- Option to allow multiple sections open simultaneously
- Custom animations and transitions
- Section icons and badges
- Lazy loading for content
- Theme customization
- Section state persistence
Understanding the Problem
What is an Accordion Component?
An accordion component is a UI pattern that:
- Organizes content into collapsible sections
- Provides a space-efficient way to display large amounts of content
- Improves user experience by reducing cognitive load
- Follows accessibility guidelines for keyboard and screen reader users
Key Concepts to Implement:
- State Management: Track which sections are open/closed
- Animation System: Smooth height transitions
- Accessibility: ARIA attributes and keyboard navigation
- Component Architecture: Reusable and extensible design
- Performance: Efficient rendering and animation
Solution Architecture
Component Structure:
Accordion/
├── AccordionContainer (Main wrapper)
├── AccordionItem (Individual section)
│ ├── AccordionHeader (Clickable header)
│ └── AccordionContent (Collapsible content)
└── AccordionProvider (Context for state management)Data Flow:
- Configuration defines accordion sections
- AccordionContainer manages overall state
- AccordionItem handles individual section behavior
- Context provides state to all child components
Complete Solution
Step 1: Define Interfaces and Types
First, let's define the structure for our accordion component:
// Accordion item interface
export interface AccordionItem {
id: string;
title: string;
content: React.ReactNode;
icon?: React.ReactNode;
disabled?: boolean;
badge?: string;
}
// Accordion configuration
export interface AccordionConfig {
items: AccordionItem[];
allowMultiple?: boolean;
defaultOpen?: string[];
animationDuration?: number;
theme?: 'light' | 'dark';
}
// Context interface
export interface AccordionContextType {
openItems: Set<string>;
toggleItem: (id: string) => void;
isItemOpen: (id: string) => boolean;
allowMultiple: boolean;
}Step 2: Create the Accordion Context
// AccordionContext.tsx
import React, { createContext, useContext, useState, useCallback } from 'react';
const AccordionContext = createContext<AccordionContextType | null>(null);
export const useAccordion = () => {
const context = useContext(AccordionContext);
if (!context) {
throw new Error('useAccordion must be used within AccordionProvider');
}
return context;
};
interface AccordionProviderProps {
children: React.ReactNode;
allowMultiple?: boolean;
defaultOpen?: string[];
}
export const AccordionProvider: React.FC<AccordionProviderProps> = ({
children,
allowMultiple = false,
defaultOpen = []
}) => {
const [openItems, setOpenItems] = useState<Set<string>>(new Set(defaultOpen));
const toggleItem = useCallback((id: string) => {
setOpenItems(prev => {
const newSet = new Set(prev);
if (newSet.has(id)) {
newSet.delete(id);
} else {
if (!allowMultiple) {
newSet.clear();
}
newSet.add(id);
}
return newSet;
});
}, [allowMultiple]);
const isItemOpen = useCallback((id: string) => {
return openItems.has(id);
}, [openItems]);
return (
<AccordionContext.Provider
value={{
openItems,
toggleItem,
isItemOpen,
allowMultiple
}}
>
{children}
</AccordionContext.Provider>
);
};Step 3: Create the Accordion Container
// AccordionContainer.tsx
import React from 'react';
import { AccordionProvider } from './AccordionContext';
import './Accordion.css';
interface AccordionContainerProps {
children: React.ReactNode;
allowMultiple?: boolean;
defaultOpen?: string[];
className?: string;
}
export const AccordionContainer: React.FC<AccordionContainerProps> = ({
children,
allowMultiple = false,
defaultOpen = [],
className = ''
}) => {
return (
<AccordionProvider allowMultiple={allowMultiple} defaultOpen={defaultOpen}>
<div className={`accordion-container ${className}`} role="region" aria-label="Accordion">
{children}
</div>
</AccordionProvider>
);
};Step 4: Create the Accordion Item
// AccordionItem.tsx
import React, { useState, useRef, useEffect } from 'react';
import { useAccordion } from './AccordionContext';
import { ChevronDownIcon } from './Icons';
interface AccordionItemProps {
id: string;
title: string;
children: React.ReactNode;
icon?: React.ReactNode;
disabled?: boolean;
badge?: string;
}
export const AccordionItem: React.FC<AccordionItemProps> = ({
id,
title,
children,
icon,
disabled = false,
badge
}) => {
const { isItemOpen, toggleItem } = useAccordion();
const [contentHeight, setContentHeight] = useState<number>(0);
const contentRef = useRef<HTMLDivElement>(null);
const isOpen = isItemOpen(id);
useEffect(() => {
if (contentRef.current) {
setContentHeight(isOpen ? contentRef.current.scrollHeight : 0);
}
}, [isOpen, children]);
const handleKeyDown = (event: React.KeyboardEvent) => {
if (disabled) return;
switch (event.key) {
case 'Enter':
case ' ':
event.preventDefault();
toggleItem(id);
break;
case 'ArrowDown':
event.preventDefault();
// Navigate to next item
break;
case 'ArrowUp':
event.preventDefault();
// Navigate to previous item
break;
}
};
return (
<div className="accordion-item">
<button
className={`accordion-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''}`}
onClick={() => !disabled && toggleItem(id)}
onKeyDown={handleKeyDown}
aria-expanded={isOpen}
aria-disabled={disabled}
aria-controls={`accordion-content-${id}`}
id={`accordion-header-${id}`}
>
<div className="accordion-header-content">
{icon && <span className="accordion-icon">{icon}</span>}
<span className="accordion-title">{title}</span>
{badge && <span className="accordion-badge">{badge}</span>}
</div>
<ChevronDownIcon className={`accordion-chevron ${isOpen ? 'rotated' : ''}`} />
</button>
<div
className="accordion-content-wrapper"
style={{ height: `${contentHeight}px` }}
>
<div
ref={contentRef}
id={`accordion-content-${id}`}
className="accordion-content"
role="region"
aria-labelledby={`accordion-header-${id}`}
>
{children}
</div>
</div>
</div>
);
};Step 5: Create the Icons Component
// Icons.tsx
import React from 'react';
interface IconProps {
className?: string;
}
export const ChevronDownIcon: React.FC<IconProps> = ({ className = '' }) => (
<svg
className={className}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4 6L8 10L12 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);Step 6: Create the CSS Styles
/* Accordion.css */
.accordion-container {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
background: white;
}
.accordion-item {
border-bottom: 1px solid #e2e8f0;
}
.accordion-item:last-child {
border-bottom: none;
}
.accordion-header {
width: 100%;
padding: 16px 20px;
background: none;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
transition: background-color 0.2s ease;
text-align: left;
}
.accordion-header:hover:not(.disabled) {
background-color: #f8fafc;
}
.accordion-header:focus {
outline: 2px solid #3b82f6;
outline-offset: -2px;
}
.accordion-header.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.accordion-header-content {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.accordion-icon {
display: flex;
align-items: center;
color: #64748b;
}
.accordion-title {
font-weight: 500;
color: #1e293b;
font-size: 16px;
}
.accordion-badge {
background: #3b82f6;
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.accordion-chevron {
transition: transform 0.2s ease;
color: #64748b;
}
.accordion-chevron.rotated {
transform: rotate(180deg);
}
.accordion-content-wrapper {
overflow: hidden;
transition: height 0.3s ease;
}
.accordion-content {
padding: 0 20px 20px;
color: #475569;
line-height: 1.6;
}
/* Dark theme */
.accordion-container.dark {
background: #1e293b;
border-color: #334155;
}
.accordion-container.dark .accordion-item {
border-color: #334155;
}
.accordion-container.dark .accordion-header:hover:not(.disabled) {
background-color: #334155;
}
.accordion-container.dark .accordion-title {
color: #f1f5f9;
}
.accordion-container.dark .accordion-content {
color: #cbd5e1;
}Step 7: Create the Main App Component
// App.tsx
import React from 'react';
import { AccordionContainer } from './AccordionContainer';
import { AccordionItem } from './AccordionItem';
const accordionData = [
{
id: 'section-1',
title: 'Getting Started',
content: (
<div>
<p>Welcome to our platform! This section will help you get started with the basics.</p>
<ul>
<li>Create your account</li>
<li>Complete your profile</li>
<li>Explore the dashboard</li>
</ul>
</div>
),
badge: 'New'
},
{
id: 'section-2',
title: 'Advanced Features',
content: (
<div>
<p>Discover advanced features that will enhance your experience.</p>
<div style={{ background: '#f1f5f9', padding: '12px', borderRadius: '6px' }}>
<strong>Pro Tips:</strong>
<ul>
<li>Use keyboard shortcuts for faster navigation</li>
<li>Customize your workspace settings</li>
<li>Enable notifications for important updates</li>
</ul>
</div>
</div>
)
},
{
id: 'section-3',
title: 'Troubleshooting',
content: (
<div>
<p>Having issues? Check out these common solutions:</p>
<details>
<summary>Connection Problems</summary>
<p>Try refreshing your browser or clearing cache.</p>
</details>
<details>
<summary>Performance Issues</summary>
<p>Close unnecessary tabs and clear browser data.</p>
</details>
</div>
)
}
];
const App: React.FC = () => {
return (
<div style={{ maxWidth: '600px', margin: '40px auto', padding: '0 20px' }}>
<h1>FAQ Accordion</h1>
<AccordionContainer defaultOpen={['section-1']}>
{accordionData.map((item) => (
<AccordionItem
key={item.id}
id={item.id}
title={item.title}
badge={item.badge}
>
{item.content}
</AccordionItem>
))}
</AccordionContainer>
<h2 style={{ marginTop: '40px' }}>Multiple Open Sections</h2>
<AccordionContainer allowMultiple={true}>
{accordionData.map((item) => (
<AccordionItem
key={`multi-${item.id}`}
id={`multi-${item.id}`}
title={item.title}
badge={item.badge}
>
{item.content}
</AccordionItem>
))}
</AccordionContainer>
</div>
);
};
export default App;Performance Optimizations
1. Memoization
const AccordionItem = React.memo<AccordionItemProps>(({ id, title, children, ...props }) => {
// Component implementation
});2. Lazy Loading
const LazyAccordionContent = React.lazy(() => import('./AccordionContent'));
// Use in AccordionItem
{isOpen && (
<Suspense fallback={<div>Loading...</div>}>
<LazyAccordionContent>{children}</LazyAccordionContent>
</Suspense>
)}3. Virtual Scrolling for Large Lists
import { FixedSizeList as List } from 'react-window';
const VirtualizedAccordion = ({ items }: { items: AccordionItem[] }) => {
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
<div style={style}>
<AccordionItem {...items[index]} />
</div>
);
return (
<List
height={400}
itemCount={items.length}
itemSize={60}
width="100%"
>
{Row}
</List>
);
};Advanced Features
1. Custom Animations
// Custom animation hook
const useAccordionAnimation = (isOpen: boolean) => {
const [height, setHeight] = useState(0);
const contentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (contentRef.current) {
const targetHeight = isOpen ? contentRef.current.scrollHeight : 0;
setHeight(targetHeight);
}
}, [isOpen]);
return { height, contentRef };
};2. Theme Support
// Theme context
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div className={`theme-${theme}`}>
{children}
</div>
</ThemeContext.Provider>
);
};3. Section State Persistence
// Local storage hook
const useAccordionPersistence = (key: string) => {
const [openItems, setOpenItems] = useState<Set<string>>(() => {
const saved = localStorage.getItem(key);
return saved ? new Set(JSON.parse(saved)) : new Set();
});
const updateOpenItems = useCallback((newItems: Set<string>) => {
setOpenItems(newItems);
localStorage.setItem(key, JSON.stringify([...newItems]));
}, [key]);
return { openItems, updateOpenItems };
};Key Takeaways
What We Learned:
- Component Architecture: How to design reusable, composable components
- State Management: Using React Context for shared state
- Accessibility: Implementing proper ARIA attributes and keyboard navigation
- Animation: Creating smooth height transitions with CSS
- Performance: Optimizing renders and animations
Best Practices:
- Always consider accessibility from the start
- Use semantic HTML and ARIA attributes
- Implement keyboard navigation
- Test with screen readers
- Optimize animations for performance
- Make components configurable and reusable
Common Pitfalls to Avoid:
- Forgetting to handle keyboard events
- Not providing proper ARIA attributes
- Using
display: noneinstead of height animations - Not testing with assistive technologies
- Making components too rigid and non-configurable
Additional Resources
- WAI-ARIA Accordion Pattern
- React Accessibility Guidelines
- CSS Animation Performance
- React Testing Library Best Practices
This accordion component demonstrates essential frontend development skills including component architecture, state management, accessibility, and performance optimization. It's a great foundation for building more complex UI components and understanding real-world development patterns.
Machine Coding Mastery: Frontend Interview Preparation
A comprehensive guide to mastering machine coding rounds for frontend development interviews with practical examples and expert strategies.
Advanced Autocomplete Search Component
Master machine coding by creating a production-ready autocomplete search with debouncing, keyboard navigation, virtual scrolling, and real-time suggestions.