An interactive React application that demonstrates core React concepts through an Accordion UI—a set of questions with toggleable answers. This project is ideal for learners who want to strengthen their understanding of React state management, component structure, and event handling. Use it as a reference for building FAQ sections, collapsible content, or any expand/collapse interface.
- Live Demo: https://accordion-display.vercel.app/
- Project Summary
- Features
- Technology Stack
- Project Structure
- Getting Started
- Environment Variables (.env)
- Component Overview & Walkthrough
- How It Works
- API, Backend & Data
- Routes & Navigation
- Code Walkthrough & Teaching Content
- Reusing Components in Other Projects
- Functionalities & Features in Detail
- Keywords & Learning Outcomes
- Conclusion
- License
This project is a fundamental React exercise focused on building a dynamic Accordion (FAQ / Q&A) interface. It covers:
- Functional components and component composition
- State management with the
useStatehook - Props and data flow from parent to child
- Conditional rendering (show/hide answer based on state)
- Event handling (click to toggle)
- Iterating over data (mapping an array to components)
There is no backend or API—all content comes from a local data file. The app is a single-page experience with no routing. You can run it locally for learning, teaching, or as a starting point for your own FAQ or accordion UI.
- Accordion behavior: List of questions; click to expand/collapse each answer.
- Per-item state: Each question manages its own open/closed state with
useState. - Visual feedback: Toggle icon switches between plus (+) and minus (-) from
react-icons. - Single-page UI: No routing; one main view with the questions list.
- Modern styling: Custom CSS with variables, shadows, and responsive-friendly layout.
- Modular structure: Separate components for the list and for each question item.
- Static data: FAQ content lives in
src/data.js; no server or environment variables required to run.
| Technology | Purpose |
|---|---|
| React 18 | UI library; components and hooks |
| Vite 4 | Dev server, HMR, and production build |
| JavaScript (ES6+) | No TypeScript; plain JS with modules |
| react-icons | Plus/minus icons for accordion toggle |
| Custom CSS | Styling in src/index.css (no CSS framework) |
Tooling: ESLint for linting; no testing framework in this repo.
04-accordion/
├── index.html # Entry HTML; root for Vite
├── package.json # Dependencies and scripts
├── vite.config.js # Vite configuration
├── eslint.config.js # ESLint (flat config)
├── .gitignore
├── README.md
├── public/
│ └── vite.svg # Favicon / default asset
└── src/
├── main.jsx # React entry; mounts App into #root
├── App.jsx # Root component; holds questions state, renders Questions
├── Questions.jsx # Renders list of SingleQuestion items
├── SingleQuestion.jsx # One accordion item (title, toggle button, answer)
├── data.js # Array of question objects (id, title, info)
└── index.css # Global and component stylesNotes:
- No
/routesor/pages: Single page only. - No
/apior backend: Data is static indata.js. - No
/hooksor/utils: Kept minimal for teaching.
- Node.js (v16+ recommended; v18+ preferred)
- npm (or yarn/pnpm)
-
Clone the repository
git clone https://github.com/arnobt78/Accordion--React-Fundamental-Project-4.git cd Accordion--React-Fundamental-Project-4 -
Install dependencies
npm install
-
Start the development server
npm run dev
The app will be at http://localhost:5173 (or the next free port if 5173 is in use).
-
Build for production
npm run build
Output goes to the
dist/folder. To preview the production build:npm run preview
-
Lint
npm run lint
This project does not use any environment variables. All content is in src/data.js, and there are no API keys or backend URLs to configure.
If you extend the project (e.g. fetch FAQs from an API), you can use Vite’s env support:
-
Create a
.envfile in the project root (same level aspackage.json). -
Define variables with the
VITE_prefix so they are exposed to the client:VITE_API_URL=https://api.example.com VITE_APP_TITLE=Accordion Display
-
Use them in code via
import.meta.env:const apiUrl = import.meta.env.VITE_API_URL; const title = import.meta.env.VITE_APP_TITLE;
-
Optional: Add
.env.example(without secrets) and document required variables for others:VITE_API_URL= VITE_APP_TITLE=Accordion Display
-
Keep
.envout of Git. This repo’s.gitignorealready includes.env, so secrets are not committed.
| Component | File | Role |
|---|---|---|
| App | src/App.jsx |
Holds questions state (from data.js), renders <Questions questions={questions} />. |
| Questions | src/Questions.jsx |
Receives questions prop; maps each item to <SingleQuestion> with a unique key. |
| SingleQuestion | src/SingleQuestion.jsx |
One accordion item: title, toggle button (plus/minus), and conditionally rendered answer. |
Data flow:
Apploads the questions array (fromdata.js) into state.Apppasses the array toQuestionsas thequestionsprop.Questionsmaps overquestionsand passes each object (e.g.id,title,info) toSingleQuestion.- Each
SingleQuestionkeeps local stateshowInfo(boolean) and toggles it on button click, showing or hiding the answer.
- Entry:
index.htmlloads/src/main.jsx.main.jsxrenders<App />inside<React.StrictMode>into#rootand importsindex.css. - Data:
Appinitializes state withuseState(data)fromdata.js. Data is an array of{ id, title, info }. - List:
Questionsreceivesquestionsand renders oneSingleQuestionper item, spreading{...question}so each getsid,title,info. - Toggle: Inside
SingleQuestion,showInfostarts asfalse. Clicking the header button callssetShowInfo(!showInfo). The answer is rendered only whenshowInfois true; the icon switches between plus and minus. - Styling:
index.cssprovides layout, colors (CSS variables), and accordion look (e.g. card, shadow, button). No separate CSS modules or component CSS files.
- API: None. No REST or GraphQL calls.
- Backend: None. The app is fully client-side.
- Data: All content is in
src/data.js—a default-exported array of objects. To change FAQs, edit that file or replace it with data from your own source (e.g. after addingfetchand optional env vars as in the Environment Variables section).
- Routes: None. Single page only; no React Router or path-based routing.
- Navigation: No internal links or route changes. The only interaction is expanding/collapsing accordion items.
The app is mounted into the DOM and global styles are applied:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);Teaching notes: createRoot is the React 18 API. StrictMode helps catch side-effect and deprecation issues during development.
State is initialized from the data module and passed down:
import { useState } from "react";
import data from "./data";
import Questions from "./Questions";
function App() {
const [questions] = useState(data);
return (
<main>
<Questions questions={questions} />
</main>
);
}
export default App;Teaching notes: useState(data) uses the imported array as initial state. Here we only need the getter; the setter is omitted because we do not update the list in this demo. To support adding/editing questions later, you would keep the setter and pass a callback or update logic.
Each item has id, title, and info:
const questions = [
{
id: 1,
title: "Do I have to allow the use of cookies?",
info: "Unicorn vinyl poutine brooklyn, next level direct trade iceland...",
},
// ... more items
];
export default questions;Teaching notes: id is used as the key when mapping in Questions.jsx. Keeping a stable, unique key helps React’s reconciliation and avoids subtle bugs when list order or content changes.
Receives the array and maps to presentational components:
import SingleQuestion from "./SingleQuestion";
const Questions = ({ questions }) => {
return (
<section className="container">
<h1>Questions</h1>
{questions.map((question) => {
return <SingleQuestion key={question.id} {...question} />;
})}
</section>
);
};
export default Questions;Teaching notes: Destructuring { questions } from props is optional but clear. key={question.id} is required for list items. {...question} passes id, title, and info to each SingleQuestion.
Local state controls visibility; icon and answer depend on that state:
import React, { useState } from "react";
import { AiOutlineMinus, AiOutlinePlus } from "react-icons/ai";
const SingleQuestion = ({ title, info }) => {
const [showInfo, setShowInfo] = useState(false);
return (
<article className="question">
<header>
<h5>{title}</h5>
<button className="question-btn" onClick={() => setShowInfo(!showInfo)}>
{showInfo ? <AiOutlineMinus /> : <AiOutlinePlus />}
</button>
</header>
{showInfo && <p>{info}</p>}
</article>
);
};
export default SingleQuestion;Teaching notes:
- useState:
showInfois local to each instance, so each accordion item opens/closes independently. - Conditional rendering:
{showInfo && <p>{info}</p>}only renders the answer whenshowInfois true. - Event handler:
onClick={() => setShowInfo(!showInfo)}toggles the boolean. - Icons: From
react-icons/ai; you can swap for other icon sets or SVGs.
The CSS uses custom properties for theming and layout (e.g. --primary-500, --shadow-2, --max-width). The .container and .question classes define the accordion layout and card appearance. No inline styles or CSS-in-JS in this project.
- Copy
SingleQuestion.jsxinto your project. - Install
react-iconsif you use the same icons:npm install react-icons. - Ensure each item has at least
titleandinfo(or rename props and adjust JSX). - Reuse the same CSS classes (e.g.
.question,.question-btn) or refactor class names and styles to match your design system.
Example in another app:
import SingleQuestion from "./SingleQuestion";
const faqs = [{ id: 1, title: "Your question?", info: "Your answer." }];
// ...
{
faqs.map((q) => <SingleQuestion key={q.id} {...q} />);
}- Copy
App.jsx,Questions.jsx,SingleQuestion.jsx, anddata.js. - Replace the contents of
data.jswith your own array (same shape:id,title,info). - Copy the relevant parts of
index.css(e.g.:rootvariables,.container,.question,.question-btn) into your global or component styles. - If you use a different icon library, replace
AiOutlinePlus/AiOutlineMinusand adjust the button content.
Lift state up: in App (or Questions), keep a single state like openId (the id of the open question). Pass openId and setOpenId (or onToggle) to Questions and then to each SingleQuestion. In SingleQuestion, use showInfo = (openId === id) and onClick = () => setOpenId(openId === id ? null : id). That way only one item is expanded at a time.
| Functionality | Where it lives | How it works |
|---|---|---|
| Load questions | App.jsx + data.js |
useState(data); data is static. |
| Render list | Questions.jsx |
questions.map(...) with key={question.id}. |
| Expand/collapse | SingleQuestion.jsx |
showInfo state; button toggles it; answer and icon depend on showInfo. |
| Icons | SingleQuestion.jsx |
react-icons AiOutlinePlus / AiOutlineMinus. |
| Styling | index.css |
Global and class-based; CSS variables for colors and spacing. |
| No backend | — | No fetch or env-based API URL; all data from data.js. |
Concepts:
- React functional components
useStatehook- Props and unidirectional data flow
- Conditional rendering
- Component composition (App → Questions → SingleQuestion)
- Event handling (onClick)
- List rendering and
key - Static data vs. future API integration
Tech:
- React 18, Vite, JavaScript (ES6+), react-icons, custom CSS
Outcomes:
- Structure a small React app with clear component boundaries.
- Manage local UI state (open/closed) inside a repeated component.
- Use props to pass data from parent to child.
- Iterate over an array and render a list of components with stable keys.
This Accordion project is a small but complete example of React fundamentals: state, props, conditional rendering, and list rendering. It is suitable for learning, teaching, or as a starting point for FAQ/collapsible UIs. The codebase is intentionally minimal so you can extend it (e.g. “single open only”, API-driven data, or different styling) without changing the core patterns.
For the full code and history, see the GitHub repository.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project—feel free to use, enhance, and extend it further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.
Enjoy building and learning! 🚀
Thank you! 😊
