Retour au catalogue

FAQ 2 Columns Sticky

FAQ en 2 colonnes avec titre sticky a gauche et questions en accordion a droite.

faqsimple Both Responsive a11y
editorialelegantsaasagencyuniversalsplitsticky
Theme
"use client";

import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronDown } from "lucide-react";

interface FaqItem {
  question: string;
  answer: string;
}

interface Faq2ColStickyProps {
  badge?: string;
  title?: string;
  subtitle?: string;
  items?: FaqItem[];
}

const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];

export default function Faq2ColSticky({
  badge = "FAQ",
  title = "Vous avez des questions ?",
  subtitle = "",
  items = [],
}: Faq2ColStickyProps) {
  const [openIndex, setOpenIndex] = useState<number | null>(0);

  return (
    <section
      className="py-20 lg:py-28"
      style={{ background: "var(--color-background)" }}
    >
      <div className="mx-auto max-w-6xl px-6">
        <div className="grid lg:grid-cols-5 gap-14 lg:gap-20">
          {/* Left - sticky */}
          <div className="lg:col-span-2 lg:sticky lg:top-24 lg:self-start">
            <motion.div
              initial={{ opacity: 0, y: 20 }}
              whileInView={{ opacity: 1, y: 0 }}
              transition={{ duration: 0.6, ease }}
              viewport={{ once: true }}
            >
              {badge && (
                <span
                  className="inline-block mb-4 text-xs font-medium tracking-widest uppercase"
                  style={{ color: "var(--color-accent)" }}
                >
                  {badge}
                </span>
              )}
              <h2
                className="text-3xl md:text-4xl font-bold"
                style={{ color: "var(--color-foreground)" }}
              >
                {title}
              </h2>
              {subtitle && (
                <p
                  className="mt-4 text-base leading-relaxed"
                  style={{ color: "var(--color-foreground-muted)" }}
                >
                  {subtitle}
                </p>
              )}
            </motion.div>
          </div>

          {/* Right - accordion */}
          <div className="lg:col-span-3">
            {items.map((item, i) => {
              const isOpen = openIndex === i;
              return (
                <motion.div
                  key={i}
                  initial={{ opacity: 0, y: 10 }}
                  whileInView={{ opacity: 1, y: 0 }}
                  transition={{ duration: 0.4, ease, delay: i * 0.04 }}
                  viewport={{ once: true }}
                  style={{
                    borderBottom: "1px solid var(--color-border)",
                  }}
                >
                  <button
                    onClick={() => setOpenIndex(isOpen ? null : i)}
                    className="w-full flex items-center justify-between gap-4 py-6 text-left"
                  >
                    <span
                      className="text-base font-medium"
                      style={{
                        color: "var(--color-foreground)",
                      }}
                    >
                      {item.question}
                    </span>
                    <motion.div
                      animate={{ rotate: isOpen ? 180 : 0 }}
                      transition={{ duration: 0.25 }}
                      className="flex-shrink-0"
                    >
                      <ChevronDown
                        size={18}
                        style={{ color: "var(--color-foreground-muted)" }}
                      />
                    </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 }}
                        className="overflow-hidden"
                      >
                        <p
                          className="pb-6 text-sm leading-relaxed"
                          style={{ color: "var(--color-foreground-muted)" }}
                        >
                          {item.answer}
                        </p>
                      </motion.div>
                    )}
                  </AnimatePresence>
                </motion.div>
              );
            })}
          </div>
        </div>
      </div>
    </section>
  );
}

Avis

FAQ 2 Columns Sticky — React Faq Section — Incubator