Building a Custom React Accordion Inspired by Material-UI
Quick answer
Build a custom React accordion by tracking which panel is open in state (the index, or null when all are closed), rendering each item with a header button that toggles it, and conditionally showing the panel body. Animate open/close with a CSS max-height or transform transition, and add aria-expanded and aria-controls for accessibility.
Accordions are popular UI components that allow users to hide and reveal content on demand. In this tutorial, we'll create a custom accordion using React and TypeScript, inspired by the elegant design of Material-UI's Accordion. We'll walk through the steps of setting up the project, implementing the component, and styling it to match the Material-UI style.
Prerequisites:
Basic knowledge of React and TypeScript Node.js and npm installed on your machine
Step 1: Project Setup
We'll begin by setting up a new React project with TypeScript using create-react-app:
npx create-react-app my-accordion-app --template typescript
cd my-accordion-app
npm startStep 2: Implementing the Accordion Component
Next, let's create a new component named "Accordion.tsx" in the "src" folder. The Accordion component will accept an array of sections as props, each containing a title and content.
// src/Accordion.tsx
import React, { useState } from 'react';
import styles from './Accordion.module.css'; // Import CSS module
interface AccordionProps {
sections: {
title: string;
content: string;
}[];
}
interface AccordionState {
activeIndex: number | null;
}
const Accordion: React.FC<AccordionProps> = ({ sections }) => {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const toggleSection = (index: number) => {
setActiveIndex((prevIndex) => (prevIndex === index ? null : index));
};
return (
<div className={styles.accordion}>
{sections.map((section, index) => (
<div key={index} className={styles['accordion-section']}>
<button
className={`${styles['accordion-toggle']} ${
index === activeIndex ? styles.active : ''
}`}
onClick={() => toggleSection(index)}
>
<span>{section.title}</span>
<span className={`${styles.icon}`}>{index === activeIndex ? '-' : '+'}</span>
</button>
<div
className={`${styles['accordion-content']} ${
index === activeIndex ? styles.activeContent : ''
}`}
>
{section.content}
</div>
</div>
))}
</div>
);
};
export default Accordion;Step 3: Styling the Accordion Component
To make our Accordion visually appealing, we'll create a CSS file named "Accordion.module.css" in the "src" folder. Here, we'll define styles for the accordion, sections, toggle buttons, and the expanding content.
/* src/Accordion.module.css */
.accordion {
width: 100%;
}
.accordion-section {
border: 1px solid #e0e0e0;
margin-bottom: 5px;
border-radius: 4px;
overflow: hidden;
}
.accordion-toggle {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
background-color: #f0f0f0;
border: none;
padding: 10px;
text-align: left;
font-weight: bold;
cursor: pointer;
}
.active {
background-color: #f5f5f5;
}
.accordion-content {
overflow: hidden;
padding: 10px;
background-color: #ffffff;
transition: max-height 0.3s ease-out;
max-height: 0;
}
.activeContent {
max-height: 1000px; /* Adjust the max height based on your content */
}
.icon {
font-size: 18px;
}
Step 4: Using the Accordion Component
With our Accordion component and styles in place, we can now use it in our main application. Update the "App.tsx" file as follows:
// src/App.tsx
import React from 'react';
import Accordion from './Accordion';
const sections = [
{
title: 'Section 1',
content: 'Content for section 1',
},
{
title: 'Section 2',
content: 'Content for section 2',
},
{
title: 'Section 3',
content: 'Content for section 3',
},
];
const App: React.FC = () => {
return (
<div>
<h1>Accordion Example</h1>
<Accordion sections={sections} />
</div>
);
};
export default App;Conclusion: In this tutorial, we built a custom accordion component in React and TypeScript, inspired by the elegant design of Material-UI's Accordion. We implemented the component with state management to toggle sections and applied CSS styles to make it visually appealing. While this implementation is not an exact replica of Material-UI's Accordion, it serves as a foundation for creating custom UI elements with similar aesthetics.
Remember, design is subjective, and you can further customize the styles and animations to match your project's specific requirements. Play around with different color schemes, add icons, or adjust transition effects to create a unique and polished accordion for your application.
Sources
Key takeaways
- •Track which panel is open with a single activeIndex state (number or null); null means every panel is collapsed.
- •Toggle by setting activeIndex to the clicked index, or back to null when it is already open, so only one panel opens at a time.
- •Animate open and close with a CSS max-height transition - height: auto cannot be animated.
- •Use a real button for each header and add aria-expanded and aria-controls for keyboard and screen-reader accessibility.
- •A scoped CSS module reproduces the Material-UI look without pulling in the Material-UI dependency.
Frequently asked questions
How do I make only one accordion panel open at a time?
Keep a single activeIndex in state and set it to the clicked panel's index; clicking the open panel again sets it back to null. Only the panel matching activeIndex renders as open.
Why will my accordion not animate open?
CSS cannot animate height: auto. Animate max-height with a transition instead, using a max-height large enough for your content when open and 0 when closed.
Do I need Material-UI to build an accordion?
No. A small React component with useState plus a CSS module gives you the same look and behaviour without the Material-UI dependency.
Software Engineering Leader & Technical Author · Updated July 21, 2026