【React】アコーディオンメニューを作成する方法(アニメーションや複数対応、入れ子(ネストする)、ライブラリなど)

2023年10月18日React

Reactでアコーディオンを作成する方法を紹介します。
まずはReactでシンプルなアコーディオンメニューの実装方法を紹介します。
その後、アニメーションの付け方や、より便利でサクッと作成できるライブラリの紹介をしています。
複数アコーディオンを設置したり、入れ子(ネストする)の場合のコードサンプルものせています。

Reactでシンプルなアコーディオンメニューを作成する方法

import { useState } from "react";

const Accordion = ({ title, children }) => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="accordion">
      <button className="accordion-header" onClick={() => setIsOpen(!isOpen)}>
        {title}
      </button>
      {isOpen && <div className="accordion-content">{children}</div>}
    </div>
  );
};

export default function App() {
  return (
    <div>
      <Accordion title="メニュー 1">
        <p>メニュー 1 の内容</p>
      </Accordion>
      <Accordion title="メニュー 2">
        <p>メニュー 2 の内容</p>
      </Accordion>
    </div>
  );
}

以下が簡単な解説です。
useState を使って開閉状態を管理します。。
button のクリックイベントでisOpen の状態を切り替えます。
isOpenがtrueの場合のみdivに内容を表示します。

Reactアコーディオンにアニメーションを追加する場合

インストール


npm install framer-motion

アニメーションを追加する場合、framer-motion を利用すると簡単です。
公式サイト: https://www.npmjs.com/package/framer-motion

実装

import { useState } from "react";
import { motion } from "framer-motion";

const Accordion = ({ title, children }) => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="accordion">
      <button onClick={() => setIsOpen(!isOpen)}>{title}</button>
      <motion.div
        initial={{ height: 0, opacity: 0 }}
        animate={{ height: isOpen ? "auto" : 0, opacity: isOpen ? 1 : 0 }}
        transition={{ duration: 0.3 }}
        style={{ overflow: "hidden" }}
      >
        {children}
      </motion.div>
    </div>
  );
};

簡単な解説です。
motion.div で height と opacity を変化させます。
animate を isOpen の状態に応じて変更します。
transition でアニメーションの速度を設定します。これで滑らかな開閉アニメーションが実現できます。
あとはプロパティをいろいろ変更してみればいろんなバリエーションのアニメーションが作成可能です。

Reactアコーディオンメニューを入れ子(ネスト)にする場合

アコーディオンをネストさせる場合の実装例です。

const NestedAccordion = ({ title, children }) => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="accordion">
      <button onClick={() => setIsOpen(!isOpen)}>{title}</button>
      {isOpen && <div className="nested">{children}</div>}
    </div>
  );
};

export default function App() {
  return (
    <NestedAccordion title="親メニュー">
      <NestedAccordion title="子メニュー 1">
        <p>子メニュー 1 の内容</p>
      </NestedAccordion>
      <NestedAccordion title="子メニュー 2">
        <p>子メニュー 2 の内容</p>
      </NestedAccordion>
    </NestedAccordion>
  );
}

親アコーディオンの children に別のアコーディオンを渡します。
入れ子になったアコーディオンが開閉可能です。

Reactのアコーディオンをより便利に簡単に使えるライブラリ

便利なライブラリを利用する方法です。

react-accessible-accordion

react-accessible-accordion は、WAI-ARIAに準拠したアクセシビリティ対応のアコーディオンライブラリです。
公式サイト: https://www.npmjs.com/package/react-accessible-accordion

インストール


npm install react-accessible-accordion

まずはnpm インストールします。

実装

import {
  Accordion,
  AccordionItem,
  AccordionItemHeading,
  AccordionItemButton,
  AccordionItemPanel,
} from "react-accessible-accordion";

export default function App() {
  return (
    <Accordion allowMultipleExpanded allowZeroExpanded>
      <AccordionItem>
        <AccordionItemHeading>
          <AccordionItemButton>メニュー 1</AccordionItemButton>
        </AccordionItemHeading>
        <AccordionItemPanel>
          <p>メニュー 1 の内容</p>
        </AccordionItemPanel>
      </AccordionItem>
    </Accordion>
  );
}

react-accessible-accordion はキーボード操作に対応できるようになります。
アクセシビリティ対応(WAI-ARIA仕様に準拠)しています。
allowMultipleExpanded で複数の項目を開けます。
allowZeroExpanded で全て閉じることも可能です。
AccordionItemPanel でコンテンツ部分を明確に分けられます。

Material-UI(MUI)

Material-UI(MUI)の Accordion コンポーネントを利用する方法です。
Material-UI(MUI)の Accordion コンポーネントを使うと、GoogleのMaterial Designに沿ったアコーディオンを作成できます。
公式サイト: https://mui.com/material-ui/getting-started/

インストール


npm install @mui/material @emotion/react @emotion/styled

実装

import { Accordion, AccordionSummary, AccordionDetails } from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";

export default function App() {
  return (
    <div>
      <Accordion>
        <AccordionSummary expandIcon={<ExpandMoreIcon />}>
          メニュー 1
        </AccordionSummary>
        <AccordionDetails>メニュー 1 の内容</AccordionDetails>
      </Accordion>

      <Accordion>
        <AccordionSummary expandIcon={<ExpandMoreIcon />}>
          メニュー 2
        </AccordionSummary>
        <AccordionDetails>メニュー 2 の内容</AccordionDetails>
      </Accordion>
    </div>
  );
}

Googleの Material Design に準拠しています。
expandIcon で簡単にアイコンを設定します。
AccordionSummary でヘッダー、AccordionDetails で内容を明確に分離できます。
カスタマイズ性が高く、公式テーマとの統一が可能です。

Bootstrap

Bootstrap を使用すると、簡単にスタイリッシュなアコーディオンを作成できます。
react-bootstrap を使用する場合は、以下の手順でセットアップします。
公式サイト: https://react-bootstrap.netlify.app/docs/getting-started/introduction

インストール


npm install react-bootstrap bootstrap

まずはインストールします。

実装

import "bootstrap/dist/css/bootstrap.min.css";
import { Accordion } from "react-bootstrap";

export default function App() {
  return (
    <Accordion defaultActiveKey="0">
      <Accordion.Item eventKey="0">
        <Accordion.Header>メニュー 1</Accordion.Header>
        <Accordion.Body>メニュー 1 の内容</Accordion.Body>
      </Accordion.Item>
      <Accordion.Item eventKey="1">
        <Accordion.Header>メニュー 2</Accordion.Header>
        <Accordion.Body>メニュー 2 の内容</Accordion.Body>
      </Accordion.Item>
    </Accordion>
  );
}

react-bootstrap の Accordion コンポーネントを使用します。
defaultActiveKey="0″ で最初のアイテムを開いた状態にできます。
Bootstrap の 一貫したデザインになるため、Bootstrapベースのプロジェクトに最適です。

Chakra UI

Chakra UI を使用すると、アクセシブルでカスタマイズ可能なアコーディオンが作成できます。
公式サイト: https://www.chakra-ui.com/docs/get-started/installation

インストール


npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion

実装

import { ChakraProvider, Accordion, AccordionItem, AccordionButton, AccordionPanel, Box } from "@chakra-ui/react";
import { AddIcon, MinusIcon } from "@chakra-ui/icons";

export default function App() {
  return (
    <ChakraProvider>
      <Accordion allowMultiple>
        <AccordionItem>
          {({ isExpanded }) => (
            <>
              <h2>
                <AccordionButton>
                  <Box flex="1" textAlign="left">
                    メニュー 1
                  </Box>
                  {isExpanded ? <MinusIcon /> : <AddIcon />}
                </AccordionButton>
              </h2>
              <AccordionPanel>メニュー 1 の内容</AccordionPanel>
            </>
          )}
        </AccordionItem>

        <AccordionItem>
          {({ isExpanded }) => (
            <>
              <h2>
                <AccordionButton>
                  <Box flex="1" textAlign="left">
                    メニュー 2
                  </Box>
                  {isExpanded ? <MinusIcon /> : <AddIcon />}
                </AccordionButton>
              </h2>
              <AccordionPanel>メニュー 2 の内容</AccordionPanel>
            </>
          )}
        </AccordionItem>
      </Accordion>
    </ChakraProvider>
  );
}

allowMultiple で複数のパネルを開くことが可能です。
AccordionButton に isExpanded を使って開閉アイコンを変更します。
スタイリッシュでカスタマイズしやすいです。

Radix UI

Radix UI はアクセシビリティ対応の軽量でアクセシビリティに優れた UI コンポーネントを利用できます。
公式サイト: https://www.radix-ui.com/themes/docs/overview/getting-started

インストール


npm install @radix-ui/react-accordion

実装

import * as Accordion from "@radix-ui/react-accordion";
import { useState } from "react";

export default function App() {
  return (
    <Accordion.Root type="single" collapsible>
      <Accordion.Item value="item-1">
        <Accordion.Header>
          <Accordion.Trigger>メニュー 1</Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Content>メニュー 1 の内容</Accordion.Content>
      </Accordion.Item>

      <Accordion.Item value="item-2">
        <Accordion.Header>
          <Accordion.Trigger>メニュー 2</Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Content>メニュー 2 の内容</Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
}

軽量でカスタマイズ自由度が高いです。
type="single" で 単一選択、type="multiple" で 複数選択 が可能です。
collapsible で開閉をトグル可能です。

Reactアコーディオンメニューライブラリのまとめ

ライブラリインストール特徴
react-accessible-accordionreact-accessible-accordionWAI-ARIA対応、アクセシビリティに優れ、シンプル
Material-UI(MUI)@mui/materialMaterial Design準拠、カスタマイズ性が高い
Bootstrapreact-bootstrapBootstrapベースのデザインと統一可能
Chakra UI@chakra-ui/react軽量かつデザイン性が高く、簡単にカスタマイズ可能
Radix UI@radix-ui/react-accordion軽量・柔軟なカスタマイズ性、アクセシビリティ対応

どのライブラリも便利ですが、デザインの統一性 や プロジェクトの要件 に合わせて選ぶと良いでしょう。

ライブラリなしで複数開閉するReactアコーディオン

複数のメニューを同時に開けるようにする方法です。

const MultiAccordion = ({ items }) => {
  const [openItems, setOpenItems] = useState({});

  const toggleItem = (index) => {
    setOpenItems((prev) => ({ ...prev, [index]: !prev[index] }));
  };

  return (
    <div>
      {items.map((item, index) => (
        <div key={index}>
          <button onClick={() => toggleItem(index)}>{item.title}</button>
          {openItems[index] && <div>{item.content}</div>}
        </div>
      ))}
    </div>
  );
};

export default function App() {
  return (
    <MultiAccordion
      items={[
        { title: "メニュー 1", content: "メニュー 1 の内容" },
        { title: "メニュー 2", content: "メニュー 2 の内容" },
      ]}
    />
  );
}

openItems というオブジェクトで開閉状態を管理します。
toggleItem で開閉を切り替えます。

ReactでQ&Aアコーディオンを作成する方法

import { AiOutlinePlus, AiOutlineMinus } from "react-icons/ai";
import { useState } from "react";

const FAQ = ({ faq }) => {
  const [showAnswer, setShowAnswer] = useState(false);
  return (
    <div className="faq" onClick={() => setShowAnswer((prev) => !prev)}>
      <div>
        <h5 className="faq__question">{faq.question}</h5>
        <button className="faq__icon">
          {showAnswer ? <AiOutlineMinus /> : <AiOutlinePlus />}
        </button>
      </div>
      {showAnswer && <p className="faq__answer">{faq.answer}</p>}
    </div>
  );
};

export default FAQ;

React Iconsのプラスアイコンとマイナスアイコンを使っています。
React Iconsの使い方は簡単で以下が実装方法です。


開いているときと、閉じているときで状態管理をしたいので、
useStateを使います。

onClick={() => setShowAnswer((prev) => !prev)}

状態の切り替えはクリックイベントで前の状態を取得して、それを反転させます。

      <div>
        <h5 className="faq__question">{faq.question}</h5>
        <button className="faq__icon">
          {showAnswer ? <AiOutlineMinus /> : <AiOutlinePlus />}
        </button>
      </div>
      {showAnswer && <p className="faq__answer">{faq.answer}</p>}

あとは、状態の変数であるshowAnswerによって、表示するものを変更します。
アイコン部分は、プラスとマイナスで切り替えるので、三項演算子を使います。
答えの部分は表示か非表示かなので、&&2つで記述します。showAnswerがtrueのときのみ次が実行されるようになります。
三項演算子や&&がわからない場合は、以下の記事を参考にしてみてください。

React

Posted by devsakaso