Back to Dictionary
Page & View Transitions
●○○Ready to Use
Recommended default

Accordion Expand

A panel that smoothly grows in height to reveal hidden content, pushing the elements below it smoothly downwards.

Use

FAQ sections

Avoid

Primary content that all users must read

Safer Alternative

No safer default listed.

Risk Level

Low risk

Live Demo

Framer Motion is a production-ready motion library for React that makes creating animations incredibly simple and declarative.

What It Is

A panel that smoothly grows in height to reveal hidden content, pushing the elements below it smoothly downwards.

When to Use

  • FAQ sections
  • Complex forms with optional sections
  • Collapsible sidebars

When NOT to Use

  • Primary content that all users must read

Configuration Tips

  • 01Animate grid-template-rows from 0fr to 1fr for performant pure CSS height animation
  • 02Rotate the chevron icon 180deg during expansion

You've Seen It In

Tailwind UIStripeNotion

AI Implementation Prompts

Move from a fast scaffold to production details, then tune timing and edge states.

Add an accordion section for FAQs. When clicking the question, the answer should slide down smoothly.

Reference Implementation

The same implementation approach used by the live preview, kept compact enough to inspect and adapt.

"use client";
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ChevronDown } from 'lucide-react';

const faqs = [
    {
        question: "What is Framer Motion?",
        answer: "Framer Motion is a production-ready motion library for React that makes creating animations incredibly simple and declarative."
    },
    {
        question: "How does the height animation work?",
        answer: "By using an AnimatePresence component and animating the height property from 0 to 'auto', we can smoothly animate the Accordion's expansion even without knowing the exact pixel height."
    },
    {
        question: "Can I use this for navigation?",
        answer: "Yes! Accordion menus are excellent for sidebar navigation, mobile menus, or anywhere you need to group related sub-items hierarchically."
    }
];

export function AccordionExpandDemo({ isPlaying = false }: { isPlaying?: boolean }) {
    const [openIndex, setOpenIndex] = useState<number | null>(0);

    useEffect(() => {
        if (!isPlaying) {
            setOpenIndex(0);
            return;
        }
        let currentIndex = 0;
        const interval = setInterval(() => {
            currentIndex = (currentIndex + 1) % faqs.length;
            setOpenIndex(currentIndex);
        }, 2000);
        return () => clearInterval(interval);
    }, [isPlaying]);

    return (
        <div className="relative flex min-h-64 h-auto w-full items-center justify-center overflow-hidden rounded-2xl bg-white p-6 shadow-sm ring-1 ring-stone-200">
            <div className="w-full max-w-md bg-white rounded-2xl shadow-sm border border-stone-200 overflow-hidden">
                {faqs.map((faq, index) => {
                    const isOpen = openIndex === index;

                    return (
                        <div key={index} className="border-b border-stone-100 last:border-b-0">
                            <button
                                onClick={() => setOpenIndex(isOpen ? null : index)}
                                className="flex w-full items-center justify-between p-4 text-left focus:outline-none focus-visible:bg-stone-50 transition-colors hover:bg-stone-50/50"
                            >
                                <span className="font-medium text-stone-900">{faq.question}</span>
                                <motion.div
                                    animate={{ rotate: isOpen ? 180 : 0 }}
                                    transition={{ duration: 0.2 }}
                                    className="text-stone-400"
                                >
                                    <ChevronDown className="h-5 w-5" />
                                </motion.div>
                            </button>

                            <AnimatePresence initial={false}>
                                {isOpen && (
                                    <motion.div
                                        initial={{ height: 0, opacity: 0 }}
                                        animate={{ height: 'auto', opacity: 1 }}
                                        exit={{ height: 0, opacity: 0 }}
                                        transition={{ duration: 0.3, ease: 'easeInOut' }}
                                    >
                                        <div className="px-4 pb-4 pt-0 text-stone-600 text-sm leading-relaxed">
                                            {faq.answer}
                                        </div>
                                    </motion.div>
                                )}
                            </AnimatePresence>
                        </div>
                    );
                })}
            </div>
        </div>
    );
}