Retour au blog

Sections FAQ React : 8 patterns d'accordéon à copier-coller

Publié le 20 mars 2026·6 min read

Les sections FAQ réduisent les tickets de support. Chaque question à laquelle vous répondez sur la page, c'est une question qui n'atterrit pas dans votre boîte mail. Mais un mur de texte ne fonctionne pas — les visiteurs scannent, ils ne lisent pas. Le pattern accordéon résout ce problème en cachant les réponses derrière des questions cliquables, permettant aux utilisateurs de trouver exactement ce qu'ils cherchent.

Ce guide couvre 8 patterns d'accordéon FAQ en React avec Tailwind CSS, d'une implémentation minimale à des layouts animés, catégorisés et multi-colonnes.

1. L'accordéon HTML natif

Avant de sortir du JavaScript, considérez les éléments <details> et <summary> intégrés au navigateur. Ils sont accessibles nativement, ne nécessitent aucune gestion d'état, et fonctionnent même avec JavaScript désactivé :

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

function FAQ({ items }: { items: FAQItem[] }) {
  return (
    <section className="mx-auto max-w-2xl px-6 py-16">
      <h2 className="text-3xl font-bold tracking-tight mb-8">
        Questions fréquentes
      </h2>
      <div className="divide-y divide-neutral-200 dark:divide-neutral-800">
        {items.map((item) => (
          <details key={item.question} className="group py-4">
            <summary className="flex cursor-pointer items-center justify-between text-left font-medium">
              {item.question}
              <span className="ml-4 transition-transform group-open:rotate-45">
                +
              </span>
            </summary>
            <p className="mt-3 text-neutral-600 dark:text-neutral-400">
              {item.answer}
            </p>
          </details>
        ))}
      </div>
    </section>
  );
}

La classe group-open:rotate-45 fait tourner le signe plus quand le détail est ouvert, le transformant en forme de x. Du CSS pur, zéro JavaScript.

2. Accordéon contrôlé avec useState

Quand vous avez besoin qu'un seul élément soit ouvert à la fois (un pattern UX courant), vous avez besoin d'état :

"use client";

import { useState } from "react";

function Accordion({ items }: { items: FAQItem[] }) {
  const [openIndex, setOpenIndex] = useState<number | null>(null);

  return (
    <div className="divide-y divide-neutral-200 dark:divide-neutral-800">
      {items.map((item, index) => (
        <div key={item.question} className="py-4">
          <button
            onClick={() => setOpenIndex(openIndex === index ? null : index)}
            className="flex w-full items-center justify-between text-left font-medium"
            aria-expanded={openIndex === index}
          >
            {item.question}
            <ChevronDown
              className={`h-5 w-5 transition-transform ${
                openIndex === index ? "rotate-180" : ""
              }`}
            />
          </button>
          {openIndex === index && (
            <p className="mt-3 text-neutral-600 dark:text-neutral-400">
              {item.answer}
            </p>
          )}
        </div>
      ))}
    </div>
  );
}

L'attribut aria-expanded indique aux technologies d'assistance si le contenu est visible. C'est essentiel pour l'accessibilité — ne le sautez pas.

3. Accordéon animé avec Framer Motion

L'accordéon contrôlé ci-dessus a un problème : la réponse apparaît et disparaît instantanément. Ajouter une animation de hauteur avec Framer Motion rend l'interaction plus soignée :

import { AnimatePresence, motion } from "framer-motion";

// Dans le map
{openIndex === index && (
  <AnimatePresence>
    <motion.div
      initial={{ height: 0, opacity: 0 }}
      animate={{ height: "auto", opacity: 1 }}
      exit={{ height: 0, opacity: 0 }}
      transition={{ duration: 0.25, ease: [0.16, 1, 0.3, 1] }}
      className="overflow-hidden"
    >
      <p className="pt-3 text-neutral-600 dark:text-neutral-400">
        {item.answer}
      </p>
    </motion.div>
  </AnimatePresence>
)}

L'animation height: "auto" est l'une des meilleures fonctionnalités de Framer Motion. Le CSS ne peut pas nativement transitionner vers une hauteur auto — il faudrait du JavaScript pour mesurer l'élément d'abord. Framer Motion gère ça en interne.

4. Accordéon multi-ouverture

Certaines sections FAQ fonctionnent mieux quand plusieurs éléments peuvent être ouverts simultanément. Remplacez le openIndex unique par un Set :

const [openIndexes, setOpenIndexes] = useState<Set<number>>(new Set());

function toggle(index: number) {
  setOpenIndexes((prev) => {
    const next = new Set(prev);
    if (next.has(index)) next.delete(index);
    else next.add(index);
    return next;
  });
}

Utilisez ce pattern quand les éléments FAQ sont courts et que les utilisateurs pourraient vouloir comparer les réponses entre les questions.

5. FAQ deux colonnes

Pour les longues listes FAQ (12+ questions), une seule colonne crée un scroll intimidant. Divisez les éléments en deux colonnes avec CSS grid :

<div className="grid gap-x-12 gap-y-0 md:grid-cols-2">
  {items.map((item, index) => (
    <AccordionItem key={index} item={item} />
  ))}
</div>

Les éléments s'écoulent de gauche à droite, de haut en bas — les éléments 1 et 2 partagent la première ligne, 3 et 4 la seconde, et ainsi de suite. Cela divise par deux la longueur perçue de la section.

6. FAQ avec catégories

Les pages FAQ produit couvrent souvent plusieurs sujets : Facturation, Fonctionnalités, Sécurité, Compte. Regroupez les questions sous des en-têtes de catégorie et laissez les utilisateurs filtrer :

const categories = ["Tout", "Facturation", "Fonctionnalités", "Sécurité", "Compte"];

function CategorizedFAQ({ items }: { items: (FAQItem & { category: string })[] }) {
  const [activeCategory, setActiveCategory] = useState("Tout");

  const filtered = activeCategory === "Tout"
    ? items
    : items.filter((item) => item.category === activeCategory);

  return (
    <section>
      <div className="flex gap-2 mb-8">
        {categories.map((cat) => (
          <button
            key={cat}
            onClick={() => setActiveCategory(cat)}
            className={`rounded-full px-4 py-1.5 text-sm font-medium transition-colors ${
              activeCategory === cat
                ? "bg-neutral-900 text-white dark:bg-white dark:text-neutral-900"
                : "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400"
            }`}
          >
            {cat}
          </button>
        ))}
      </div>
      <Accordion items={filtered} />
    </section>
  );
}

Le sélecteur de catégorie en style pilule est plus invitant qu'un dropdown. Les utilisateurs voient toutes les catégories d'un coup d'oeil et basculent instantanément.

7. FAQ avec recherche

Pour les bases de connaissances avec 50+ questions, ajoutez un champ de recherche au-dessus de l'accordéon. Filtrez les éléments côté client avec un simple check includes sur le texte de la question et de la réponse :

const [query, setQuery] = useState("");

const filtered = items.filter(
  (item) =>
    item.question.toLowerCase().includes(query.toLowerCase()) ||
    item.answer.toLowerCase().includes(query.toLowerCase())
);

Affichez un message "Aucun résultat" quand la liste filtrée est vide. Évitez de cacher le champ de recherche derrière un toggle — si la FAQ est assez longue pour justifier une recherche, le champ doit toujours être visible.

8. FAQ avec balisage structuré

Google peut afficher le contenu FAQ directement dans les résultats de recherche sous forme de rich snippets. Ajoutez des données structurées JSON-LD à votre section FAQ :

function FAQSchema({ items }: { items: FAQItem[] }) {
  const schema = {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    mainEntity: items.map((item) => ({
      "@type": "Question",
      name: item.question,
      acceptedAnswer: {
        "@type": "Answer",
        text: item.answer,
      },
    })),
  };

  // Sérialisez le schéma en JSON-LD et injectez-le
  // dans une balise <script type="application/ld+json">
  return null;
}

Placez ce composant à côté de votre section FAQ. Les données structurées n'affectent pas le rendu visuel mais indiquent aux moteurs de recherche que votre page contient du contenu FAQ éligible aux résultats enrichis.

Checklist accessibilité

  • Utilisez des éléments <button> pour les déclencheurs d'accordéon, pas des <div> avec onClick
  • Incluez aria-expanded sur chaque déclencheur
  • Utilisez aria-controls liant le bouton à l'id du panneau
  • Assurez-vous que la navigation clavier fonctionne — Entrée et Espace doivent toggler les éléments
  • Maintenez des indicateurs de focus visibles sur les déclencheurs

Sections FAQ prêtes à l'emploi

Construire une FAQ accessible et animée from scratch prend du temps que vous pourriez consacrer à votre produit. Le catalogue FAQ d'Incubator propose 10+ sections FAQ prêtes à l'emploi — accordéons, deux colonnes, catégorisées, avec recherche, avec balisage structuré — le tout en React et Tailwind CSS.

Explorez la bibliothèque de composants complète pour chaque section dont votre landing page a besoin, des heroes au pricing.

VA

Victor Aubague

Développeur & créateur d'Incubator

Développeur full-stack spécialisé en React, Next.js et TypeScript. J'ai créé Incubator pour aider les développeurs à livrer de belles interfaces plus rapidement — tous les composants sont issus de vrais projets clients et de code en production.

LinkedIn
Sections FAQ React : 8 patterns d'accordéon à copier-coller — Incubator