The musical instrument accordion compresses and expands. The graphical control element accordion similarly collapses and expands. This can be a solution for breaking up large content and allow users to focus on sections that are relevant to them.
Similar to a tabs component, an accordion component is comprised of different sections that can be toggled to be open and closed. Unlike a tabs component, accordions can support displaying multiple sections of content at the same time.
In this article, you will create a simple reusable accordion component with sections that can be toggled open and closed. We will then modify the component to support having multiple sections opened at once and specifying which sections should be open by default.
To complete this tutorial, you will need:
This tutorial was verified with Node v16.6.2, npm
v7.21.0, and react
v17.0.2.
In this step, you’ll create a new project using Create React App. You will then delete the sample project and related files that are installed when you bootstrap the project.
To start, make a new project. In your terminal, run the following script to install a fresh project using create-react-app
:
- npx create-react-app react-accordion-component
After the project is finished, change into the directory:
- cd react-accordion-component
In a new terminal tab or window, start the project using the Create React App start script. The browser will auto-refresh on changes, so leave this script running while you work:
- npm start
This will start a locally running server. If the project did not open in a browser window, you can open it by visiting http://localhost:3000/
. If you are running this from a remote server, the address will be http://your_domain:3000
.
Your browser will load with a template React application included as part of Create React App:
You will be building a completely new set of custom components, so you’ll need to start by clearing out some boilerplate code so that you can have an empty project.
To start, open src/App.js
in a text editor. This is the root component that is injected into the page. All components will start from here. You can find more information about App.js
at How To Set Up a React Project with Create React App.
You will see a file like this:
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
Delete the line import logo from './logo.svg';
. Then replace everything in the return
statement to return a set of div
tags and an h1
. This will give you a valid page that returns an h1
that displays Accordion Demo. The final code will look like this:
import './App.css';
function App() {
return (
<div>
<h1>Accordion Demo</h1>
</div>
);
}
export default App;
Save and exit the text editor.
Finally, delete the logo. You won’t be using it in your application, and you should remove unused files as you work. It will save you from confusion in the long run.
In the terminal window type the following command to delete the logo:
- rm src/logo.svg
Now that the project is set up, you can create your first component.
We will be creating three components:
Accordion
: which will hold our section components and manage which sections are open and closed.AccordionSection
: which will display the clickable section title, the section contents when the section is open, and report back to the Accordion
about click events.App
: component to tie everything together into a working example!The sections inside of the Accordion
component will simply be <div>
tags that will receive a label
attribute that will be used for the clickable region inside of the AccordionSection
component.
Let’s start by creating our innermost component, AccordionSection
:
import React from 'react';
import PropTypes from 'prop-types';
class AccordionSection extends React.Component {
static propTypes = {
children: PropTypes.instanceOf(Object).isRequired,
isOpen: PropTypes.bool.isRequired,
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
onClick = () => {
this.props.onClick(this.props.label);
};
render() {
const {
onClick,
props: { isOpen, label },
} = this;
return (
<div
style={{
background: isOpen ? '#fae042' : '#6db65b',
border: '1px solid #008f68',
padding: '5px 10px',
}}
>
<div onClick={onClick} style={{ cursor: 'pointer' }}>
{label}
<div style={{ float: 'right' }}>
{!isOpen && <span>▲</span>}
{isOpen && <span>▼</span>}
</div>
</div>
{isOpen && (
<div
style={{
background: '#6db65b',
border: '2px solid #008f68',
marginTop: 10,
padding: '10px 20px',
}}
>
{this.props.children}
</div>
)}
</div>
);
}
}
export default AccordionSection;
This component will receive a label
property that will create the clickable title of the section. The onClick
event handler property is used to report back to the parent component if the label has been clicked.
The parent component will handle keeping track of which sections are opened or closed and feeds the status back to AccordionSection
as a boolean property, isOpen
.
Any child components of AccordionSection
will be displayed only if the section has been toggled to open and the property isOpen
is true.
Now that we have a component for our sections, we need a component that will house these sections and manage the state of which sections have been opened or closed:
import React from 'react';
import PropTypes from 'prop-types';
import AccordionSection from './AccordionSection';
class Accordion extends React.Component {
static propTypes = {
children: PropTypes.instanceOf(Object).isRequired,
};
constructor(props) {
super(props);
const openSections = {};
this.state = { openSections };
}
onClick = label => {
const {
state: { openSections },
} = this;
const isOpen = !!openSections[label];
this.setState({
openSections: {
[label]: !isOpen
}
});
};
render() {
const {
onClick,
props: { children },
state: { openSections },
} = this;
return (
<div style={{ border: '2px solid #008f68' }}>
{children.map(child => (
<AccordionSection
isOpen={!!openSections[child.props.label]}
label={child.props.label}
onClick={onClick}
>
{child.props.children}
</AccordionSection>
))}
</div>
);
}
}
export default Accordion;
Great! Our Accordion
component will receive child nodes that will be used to create our interactive AccordionSection
components.
This component tracks which section has been toggled opened but only keeps track of a single section being open at a time (for now).
Having created our Accordion
and AccordionSection
components, we can now create our App
component and see things in action!
For our accordion demo, we will create an Accordion
with two child components that contain some facts about Alligators:
import Accordion from './Accordion';
function App() {
return (
<div>
<h1>Accordion Demo</h1>
<Accordion>
<div label='Alligator Mississippiensis'>
<p>
<strong>Common Name:</strong> American Alligator
</p>
<p>
<strong>Distribution:</strong> Texas to North Carolina, US
</p>
<p>
<strong>Endangered Status:</strong> Currently Not Endangered
</p>
</div>
<div label='Alligator Sinensis'>
<p>
<strong>Common Name:</strong> Chinese Alligator
</p>
<p>
<strong>Distribution:</strong> Eastern China
</p>
<p>
<strong>Endangered Status:</strong> Critically Endangered
</p>
</div>
</Accordion>
</div>
);
}
export default App;
The Accordion
contains two sections that, upon being clicked open, will display some facts about different species of alligators.
Everything we have created thus far is usable, but somewhat limited as only a single section can be open at a time, and every section is collapsed by default.
To add support for multiple sections being open at the same time, we’re going to add a property named allowMultipleOpen
to the Accordion
component which will be used tell the accordion if it should allow multiple sections to be open.
When enabled, the state will toggle individual keys true/false instead of completely overwriting the state with the section that was interacted with.
While we’re in there, we can add some additional logic to check the child nodes for an isOpen
property. When present, the open state will be initialized with the section being marked as open already:
import React from 'react';
import PropTypes from 'prop-types';
import AccordionSection from './AccordionSection';
class Accordion extends React.Component {
static propTypes = {
allowMultipleOpen: PropTypes.bool,
children: PropTypes.instanceOf(Object).isRequired,
};
constructor(props) {
super(props);
const openSections = {};
this.props.children.forEach(child => {
if (child.props.isOpen) {
openSections[child.props.label] = true;
}
});
this.state = { openSections };
}
onClick = label => {
const {
props: { allowMultipleOpen },
state: { openSections },
} = this;
const isOpen = !!openSections[label];
if (allowMultipleOpen) {
this.setState({
openSections: {
...openSections,
[label]: !isOpen
}
});
} else {
this.setState({
openSections: {
[label]: !isOpen
}
});
}
};
render() {
const {
onClick,
props: { children },
state: { openSections },
} = this;
return (
<div style={{ border: '2px solid #008f68' }}>
{children.map(child => (
<AccordionSection
isOpen={!!openSections[child.props.label]}
label={child.props.label}
onClick={onClick}
>
{child.props.children}
</AccordionSection>
))}
</div>
);
}
}
export default Accordion;
With our Accordion
ready to accept new parameters, we can update our App
component to pass in the property to allow multiple sections to be open as well as flagging the first section to be open by default:
import Accordion from './Accordion';
function App() {
return (
<div>
<h1>Accordion Demo</h1>
<Accordion allowMultipleOpen>
<div label='Alligator Mississippiensis' isOpen>
<p>
<strong>Common Name:</strong> American Alligator
</p>
<p>
<strong>Distribution:</strong> Texas to North Carolina, US
</p>
<p>
<strong>Endangered Status:</strong> Currently Not Endangered
</p>
</div>
<div label='Alligator Sinensis'>
<p>
<strong>Common Name:</strong> Chinese Alligator
</p>
<p>
<strong>Distribution:</strong> Eastern China
</p>
<p>
<strong>Endangered Status:</strong> Critically Endangered
</p>
</div>
</Accordion>
</div>
);
}
export default App;
There you have it, a reusable accordion component to help you tame unruly content.
In this article, you created a simple reusable accordion component with sections that can be toggled open and closed.
Continue your learning by nesting these accordions to satisfy even the most complex scenarios.
You can find a working demo and code from this article over on CodeSandbox.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!