Sticky FAQ Accordion
An FAQ section with a sticky heading and a native details accordion that opens one answer at a time. Tailwind CSS, no JavaScript needed.
StickyFAQAccordion.tsx
// FAQ: sticky heading on the left, questions on the right opening one at a time.
// Native <details> (no JavaScript); answers animate open where the browser supports it.
// Pure Tailwind, no dependencies. Works with Tailwind v3 and v4.
type Faq = { question: string; answer: string };
type Props = {
heading?: string[];
intro?: string;
link?: { label: string; href: string };
faqs?: Faq[];
};
const DEFAULT_FAQS: Faq[] = [
{ question: "What does a weekly charter rate include?", answer: "The rate covers the yacht, her crew and their insurance. Fuel, food and drink, berthing and other running costs are paid through an Advance Provisioning Allowance of around 30–35% of the rate, with every euro accounted for at the end." },
{ question: "How far in advance should we book?", answer: "For July and August in the Mediterranean, or Christmas in the Caribbean, we recommend booking nine to twelve months ahead. Shoulder seasons can often be arranged at a few weeks' notice." },
{ question: "Can we design our own itinerary?", answer: "Always. We draft a route around your wishes, and your captain adjusts it day by day for the weather. Nothing is fixed: if you love an anchorage, you stay." },
{ question: "Are children and pets welcome?", answer: "Most of our yachts welcome children, with nannies, children's menus and junior water sports on request. Pets are welcome on selected yachts." },
{ question: "Is a deposit required?", answer: "A 50% deposit secures your dates, with the balance due one month before embarkation. We recommend cancellation insurance, which we can arrange for you." },
];
export default function StickyFaqAccordion({
heading = ["Good to", "know"],
intro = "Everything you might wonder before your first charter. Anything else, just ask.",
link = { label: "Ask us anything", href: "#" },
faqs = DEFAULT_FAQS,
}: Props) {
return (
<section className="bg-white py-28 font-['Helvetica_Neue',Helvetica,Arial,sans-serif] text-[#0d1114] antialiased md:py-40">
<style>{`
details[data-faq]::details-content {
block-size: 0;
overflow: clip;
transition: block-size 0.7s cubic-bezier(0.22, 1, 0.36, 1), content-visibility 0.7s allow-discrete;
}
details[data-faq][open]::details-content { block-size: auto; }
@supports (interpolate-size: allow-keywords) { :root { interpolate-size: allow-keywords; } }
`}</style>
<div className="mx-auto grid max-w-[100rem] gap-14 px-4 md:px-8 lg:grid-cols-12 lg:px-12">
<div className="flex flex-col items-start gap-7 lg:sticky lg:top-32 lg:col-span-4 lg:self-start">
<h2 className="text-[clamp(1.875rem,3.3vw,3.5rem)] font-normal leading-[1.05] tracking-[-0.03em]">
{heading.map((line, i) => (
<span key={i} className="block">
{line}
</span>
))}
</h2>
{intro && <p className="text-base leading-relaxed text-[#0d1114]/60">{intro}</p>}
{link && (
<a href={link.href} className="group inline-flex items-center gap-3 border-b border-[#0d1114]/25 pb-1 text-xs font-medium uppercase tracking-[0.08em] transition-colors hover:border-[#0d1114]">
{link.label}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.1} aria-hidden className="h-4 w-4 transition-transform duration-500 group-hover:translate-x-1">
<path d="M3 12h17M14 6l6 6-6 6" />
</svg>
</a>
)}
</div>
<div className="border-t border-[#0d1114]/10 lg:col-span-7 lg:col-start-6">
{faqs.map((item, i) => (
<details key={item.question} data-faq name="faq" open={i === 0} className="group border-b border-[#0d1114]/10">
<summary className="flex cursor-pointer list-none items-center justify-between gap-8 py-7 md:py-8 [&::-webkit-details-marker]:hidden">
<h3 className="text-2xl font-normal transition-colors duration-500 group-hover:text-[#876c43] md:text-3xl">{item.question}</h3>
<span className="grid h-10 w-10 shrink-0 place-items-center rounded-full border border-[#0d1114]/20 transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] group-open:rotate-45">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.1} aria-hidden className="h-4 w-4">
<path d="M12 4v16M4 12h16" />
</svg>
</span>
</summary>
<p className="max-w-2xl pb-9 text-base leading-relaxed text-[#0d1114]/65">{item.answer}</p>
</details>
))}
</div>
</div>
</section>
);
}
How to use it
- Make sure your Next.js or React project has Tailwind CSS set up.
- Create
components/StickyFAQAccordion.tsxand paste the code. - Import it into a page and replace the placeholder text and images with your own.
More free components
All faqs →
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


