Simple accordion menu in react

Without any external library or CSS framework

chevron

Table of contents

An overview

The main idea behind building an accordion menu in React is to create a component that represents each of the accordion list elements and manipulate it using CSS properties such as height and transform translateY(). In this way, we can collapse and expand the accordion elements smoothly and efficiently. In this article, we will guide you through the process of building this component, providing detailed explanations every step of the way.

Creating the AccordionItem component

To begin with, we will create a simple component that can be imported later into our parent component. This component will serve as the basic building block for our accordion menu, allowing us to add and style each accordion element as needed.

const AccordionItem = () => {
  return (
    <div>
      Accordion item
    </div>
  )
}
export default AccordionItem

The parent element

With our basic accordion element component in place, we can now move on to building the parent component. For the purposes of this tutorial, we will be using the `App` component. However, before we can do this, we will need some dummy data to work with. To create this data, let's create a new file called `data.js` and add an array of sample data to it. This data will serve as the content for our accordion menu.

export const data = [
  {
    id: 0,
    question: 'Question one',
    answer: 'Answer for question one'
  },
  {
    id: 1,
    question: 'Question two',
    answer: 'Answer for question two'
  },
  {
    id: 2,
    question: 'Question three',
    answer: 'Answer for question three which is a longer answer than the rest answers'
  }
]

Once we have our dummy data in place, we can import it along with our `AccordionItem` component into the `App` component. Using the `map` function, we can then iterate over our data and return a list of `AccordionItem` components, each with their own unique content. Here is an example of what the final code for the `App` component might look like:

const App = () => {
  return (
    <div className='App'>
      <h1>Simple Accordion</h1>
      <div className='accordion-container'>
        {data.map((item) => {
          const { id, question, answer } = item
          return (
            <AccordionItem 
              key={id} 
              question={question} 
              answer={answer} 
            />
          )
        })}
      </div>
    </div>
  )
}
export default App

AccordionItem component

To implement the accordion functionality, we will need to use the `useState` hook to handle opening and closing the content of each accordion item. Additionally, we will need to include an icon to indicate the state of each item. For this tutorial, we will be using the `react-icons` library, but any icon library will work.

The `AccordionItem` component is composed of two main elements: the header, which is always visible, and the content, which can be toggled open and closed. To achieve the desired accordion effect, we will translate the content upwards so that it is hidden behind the header, taking advantage of CSS transitions which do not work on elements with `display: none;`. With these concepts in mind, the final version of the `AccordionItem` component should look something like this:

import { useState } from 'react'
import { VscChevronDown } from 'react-icons/vsc'

const AccordionItem = ({ question, answer }) => {
  const [isOpen, setIsOpen] = useState(false)

  const handleClick = () => {
    setIsOpen((v) => !v)
  }

  return (
    <div className={isOpen ? 'item-open' : 'item'}>
      <div className='item-head' onClick={handleClick}>
        <h3>{question}</h3>
        <div className='icon-container'>
          <VscChevronDown className={isOpen ? 'icon icon-open' : 'icon'} />
        </div>
      </div>
      <div
        className={isOpen ? 'item-content item-content-open' : 'item-content'}
      >
        <p>{answer}</p>
      </div>
    </div>
  )
}

export default AccordionItem

The styles

Now that we have our `AccordionItem` component in place, we can add the necessary styles to implement the accordion functionality. First, we need to remove any default padding or margins to ensure that our component behaves consistently across different browsers and devices.

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

Next, we will style the parent container of our `AccordionItem` components. To do this, we will add a CSS variable called `--item-height` that controls the height of each `AccordionItem`. By using a variable, we can easily change the height of all `AccordionItem` components at once, making our code more modular and maintainable.

.accordion-container {
  display: flex;
  align-items: center;
  flex-direction: column;
  gap: 20px;
  padding: 50px 10px;
  --item-height: 80px;
}

Next we style the `AccordionItem` main container.

.item, .item-open {
  width: 600px;
  transition: all 0.3s ease-in-out;
  border-radius: 5px;
}

Once we have set up the CSS variable for the height of our `AccordionItem` components, we can set the height of the main container based on its current state (open or closed). When an `AccordionItem` is closed, its content will be hidden behind the header and the height of the main container will be equal to the height of the header. When an `AccordionItem` is open, the content will be displayed and the height of the main container will be equal to the height of the header plus the height of the content.

.item {
  height: var(--item-height);
}
.item-open {
  height: calc(var(--item-height) * 2);
}

To achieve the desired accordion effect, we will also need to style the header of each `AccordionItem`. Specifically, we will set the height of both the icon container and the title container to be the same as the height of the header container `(item-head)`. Additionally, we will set the `z-index` of the header to be `1`, so that the content of the `AccordionItem` is directly beneath the header when it is hidden.

Finally, we will add a rotating animation to the icon to indicate when the content is visible. This animation will be triggered by toggling a CSS class based on the open or closed state of the `AccordionItem`.

.item-head {
  display: flex;
  cursor: pointer;
  border-radius: 5px;
}
.item-head h3, .icon-container {
  background-color: #f5f5f5;
  height: var(--item-height);
  display: flex;
  align-items: center;
  z-index: 1;
  font-weight: 500;
  border-radius: 5px;
  padding: 0 25px;
}
.item-head h3 {
  width: 100%;
  font-size: 20px;
}
.icon {
  font-size: 24px;
  transition: transform 0.3s ease-in-out;
}
.icon-open {
  transform: rotate(-180deg);
}

Finally, we need to style the content container of each `AccordionItem`. The content container should have the same height as the `item-content` container. To achieve the accordion effect, we will translate the content container upwards by its height when it is initially closed or closed again. When the content is visible, we will remove the translation and set the container's position to `0` so that it appears directly beneath the header. This combination of translations and positioning will create a smooth accordion effect when the `AccordionItem` is toggled open and closed.

.item-content {
  background-color: #f5f5f5;
  height: var(--item-height);
  display: flex;
  align-items: center;
  padding: 0 25px;
  transform: translateY(calc(-1 * var(--item-height)));
  transition: all 0.3s ease-in-out;
  border-top: 1px solid #dedede;
  border-radius: 5px;
}
.item-content p {
  color: #111111;
  font-size: 16px;
}
.item-content-open {
  transform: translateY(0);
  border-top-right-radius: 0;
  border-top-left-radius: 0;
}

The final result should look like this:

Simple accordion menu in react blog image

Conclusion

In this article, we have walked through a simple implementation of an accordion menu using React and CSS. While this solution may not meet every use case, it provides a straightforward vanilla solution that can be adapted and customized to suit your needs. Thank you for reading, and I hope you found this article helpful in building your own accordion menu component.