Tutorial

How To Build an Accordion Component with React

React

Introduction

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.

Prerequisites

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.

Step 1 — Setting Up the Project

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:

React template project

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:

src/App.js
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:

src/App.js
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.

Step 2 — Building the Components

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:

src/AccordionSection.js
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>&#9650;</span>}
            {isOpen && <span>&#9660;</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:

src/Accordion.js
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:

src/App.js
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.

Step 3 — Supporting Multiple Open Accordions

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:

src/Accordion.js
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:

src/App.js
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.

Conclusion

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.

Creative Commons License