Reusable Accordion Component | React, Tailwind CSS

, ,

|

|

|

4 minutes

An accordion — a versatile UI pattern that could elegantly expand and collapse content with a mere click. We write this reusable component using React and styled with Tailwind CSS. Let’s dissect it step by step:

Component Structure

The accordion is composed of three main parts:

  1. Accordion: The parent component that holds the accordion items.
  2. AccordionHeader: Represents the clickable header of each accordion item.
  3. AccordionPanel: Contains the content that expands or collapses when the header is clicked.

1. Accordion Component (Accordion.jsx)

import React, { useState } from "react";

export const Accordion = ({ children }) => {
  const [accordionOpen, setAccordionOpen] = useState(false);

  const toggleAccordion = () => {
    setAccordionOpen(!accordionOpen);
  };

  return (
    <div className="accordion py-3 border-b last:border-b-0 border-slate-300">
      {children.map((child, index) => {
        if (child.type.name === "AccordionHeader") {
          return React.cloneElement(child, {
            key: index,
            accordionOpen,
            toggleAccordion,
          });
        } else if (child.type.name === "AccordionPanel") {
          return React.cloneElement(child, {
            key: index,
            accordionOpen,
          });
        } else {
          return child;
        }
      })}
    </div>
  );
};

Explanation:

  • The Accordion component receives a children prop, which is an array of accordion items (headers and panels).
  • It manages the state of whether an accordion item is open or closed using the accordionOpen state variable.
  • The toggleAccordion function toggles the state when an accordion header is clicked.
  • It maps over the children and renders each item (header or panel).
  • The React.cloneElement method is used to pass additional props (accordionOpen and toggleAccordion) to the child components.

2. AccordionHeader Component

export const AccordionHeader = ({ children, accordionOpen, toggleAccordion, iconOpen = <IconOpen/>, iconClose = <IconClose/> }) => {
  const icon = accordionOpen ? iconOpen : iconClose;

  return (
    <div className="accordion-header">
      <button className="accordion-btn flex justify-between w-full" onClick={toggleAccordion}>
        {children} <span>{icon}</span>
      </button>
    </div>
  );
};

// Icon: Open
const IconOpen = () => (
    <svg stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 1024 1024" height="1em" width="1em"
         xmlns="http://www.w3.org/2000/svg">
        <path d="M872 474H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"></path>
    </svg>
)

// Icon: Close
const IconClose = () => (
    <svg stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 1024 1024" height="1em" width="1em"
         xmlns="http://www.w3.org/2000/svg">
        <path d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8Z"></path>
        <path d="M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8Z"></path>
    </svg>
)

Explanation:

  • The AccordionHeader component represents the clickable header for each accordion item.
  • It receives the following props:
    • children: The content inside the header (e.g., “Accordion item 1”).
    • accordionOpen: Indicates whether the item is open or closed.
    • toggleAccordion: Function to toggle the accordion state.
  • The header includes a button with a customizable icon (default icons are “+” for closed and “-” for open).

3. AccordionPanel Component

export const AccordionPanel = ({ children, accordionOpen }) => {
  const className = accordionOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0";

  return (
    <div className={`grid overflow-hidden transition-all duration-300 ease-in-out text-slate-600 text-sm ${className}`}>
      <div className="overflow-hidden">
        <div className="accordion-panel-content pt-3 pr-3">{children}</div>
      </div>
    </div>
  );
};

Explanation:

  • The AccordionPanel component contains the content that expands or collapses.
  • It receives the accordionOpen state to determine whether to show or hide the content.
  • The className dynamically adjusts based on the accordion state (open or closed).
  • The content is wrapped in a grid with a smooth transition effect.

Good to know

In brief, the state and the tailwind classes manages the visibility and layout of the accordion panel content based on whether the item is open or closed. It adjusts the grid rows and opacity accordingly, while the overflow-hidden class ensures that any excess content remains hidden within the panel container. 🎨

  1. const className = accordionOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0";
    • This line creates a variable called className based on the value of accordionOpen.
    • The accordionOpen variable represents whether the accordion item is currently open or closed.
    • The ternary operator (? :) is used to conditionally assign a value to className:
      • If accordionOpen is true, the class string "grid-rows-[1fr] opacity-100" is assigned.
      • If accordionOpen is false, the class string "grid-rows-[0fr] opacity-0" is assigned.
    • Let’s break down the classes:
      • "grid-rows-[1fr]": This class sets the grid rows to take up all available vertical space (1 fraction of the available space). It ensures that the panel content expands to fill the available height when the accordion item is open.
      • "opacity-100": This class sets the opacity of the element to 100%, making it fully visible.
      • "grid-rows-[0fr]": When the accordion item is closed, this class sets the grid rows to take up no vertical space (0 fractions). The panel content effectively disappears.
      • "opacity-0": When closed, this class sets the opacity to 0%, making the element completely invisible.
  2. className="overflow-hidden"
    • The overflow-hidden class is applied directly to the container element (presumably the parent of the panel content).
    • Its purpose is to hide any content that overflows the container’s boundaries. In the context of an accordion, this ensures that when the panel is collapsed (and its height is reduced), any content that would otherwise overflow is clipped and not visible.
    • Essentially, it prevents the panel content from spilling outside its designated area, maintaining a clean and consistent appearance.

How to Use

import { Accordion, AccordionHeader, AccordionPanel } from "./components/Accordion";

export default function App() {
  return (
    <div className="w-full h-screen bg-gradient-to-r from-indigo-500 to-blue-600 p-4">
      <div className="py-2 px-6 bg-gray-200 rounded-lg">
        <Accordion>
          <AccordionHeader iconOpen="🔼" iconClose="🔽">Accordion item 1</AccordionHeader>
          <AccordionPanel>
            Duis aute irure dolor in reprehenderit in voluptate velit esse
            cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
            cupidatat non proident, sunt in culpa qui officia deserunt mollit
            anim id est laborum.
          </AccordionPanel>
        </Accordion>
        <Accordion>
          <AccordionHeader>Accordion item 2</AccordionHeader>
          <AccordionPanel>
            ...
          </AccordionPanel>
        </Accordion>
        <Accordion>
          <AccordionHeader>Accordion item 3</AccordionHeader>
          <AccordionPanel>
            ...
          </AccordionPanel>
        </Accordion>
      </div>
    </div>
  );
}

Conclusion

By following these patterns, we’ve created a reusable accordion component that adapts to various use cases. Remember to customize the styling and behavior according to your project requirements. Check out the live demo on codesandbox.

Happy coding! 🚀