Accordion FAQs
An FAQ accordion that opens one answer at a time with a smooth CSS grid height animation. React and Tailwind CSS, no dependencies.
AccordionFAQs.tsx
"use client";
// FAQ accordion: one answer open at a time, smooth height animation via CSS grid rows.
// Pure Tailwind + React, no dependencies. Works with Tailwind v3 and v4.
import { useState } from "react";
type Faq = { question: string; answer: string };
type Props = {
heading?: string;
faqs?: Faq[];
};
const DEFAULT_FAQS: Faq[] = [
{ question: "How do I search for properties on your website?", answer: "Use the search bar to filter by location, price range, property type and more, then browse the listings that match. Save a search to get new matches by email." },
{ question: "Are the listings updated regularly?", answer: "Yes. Listings sync with our agents' systems throughout the day, so what you see is what's available." },
{ question: "Can I book viewings directly through the website?", answer: "Absolutely. Every listing has a 'Book a viewing' button that shows the agent's available times." },
{ question: "Do you help with financing and mortgages?", answer: "We work with trusted lenders and can introduce you to a mortgage adviser who will walk you through your options." },
{ question: "What types of properties do you specialise in?", answer: "Family homes, apartments and new builds across the city and surrounding suburbs, for both sale and rent." },
];
export default function AccordionFaqs({ heading = "Frequently asked questions", faqs = DEFAULT_FAQS }: Props) {
const [openIndex, setOpenIndex] = useState<number | null>(0);
return (
<section className="bg-white px-5 py-20 font-['Manrope',ui-sans-serif,system-ui,sans-serif] antialiased md:px-10">
<div className="mx-auto max-w-5xl">
<h2 className="mb-10 text-center font-['Bricolage_Grotesque',ui-sans-serif,system-ui,sans-serif] text-[clamp(32px,4vw,50px)] font-semibold leading-[1.2] text-black">{heading}</h2>
<div className="divide-y divide-neutral-200 border-y border-neutral-200">
{faqs.map((item, index) => {
const open = openIndex === index;
return (
<div key={item.question} className="py-1 md:py-4">
<button
type="button"
onClick={() => setOpenIndex(open ? null : index)}
aria-expanded={open}
className="flex w-full items-center justify-between gap-6 py-4 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-black/40"
>
<span className="text-xl font-medium text-neutral-900 md:text-2xl">{item.question}</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75} aria-hidden className={`h-7 w-7 shrink-0 text-neutral-500 transition-transform duration-300 ${open ? "" : "rotate-180"}`}>
<circle cx="12" cy="12" r="10" />
<path d="m8 14 4-4 4 4" />
</svg>
</button>
<div className={`grid transition-all duration-300 ease-in-out ${open ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"}`}>
<div className="overflow-hidden">
<p className="pb-4 text-base leading-relaxed text-neutral-600 md:text-lg">{item.answer}</p>
</div>
</div>
</div>
);
})}
</div>
</div>
</section>
);
}
How to use it
- Make sure your Next.js or React project has Tailwind CSS set up.
- Create
components/AccordionFAQs.tsxand paste the code. - Import it into a page and replace the placeholder text and images with your own.
More free components
All accordions →
Ready for a faster site that's easier to edit?
Book a free 30-minute call. We'll review your current site and send a fixed-price plan. No pressure, no pitch.
Book Free Call No commitment required


