Las secciones FAQ reducen los tickets de soporte. Cada pregunta que respondes en la página es una pregunta que no llega a tu bandeja de entrada. Pero un muro de texto no funciona: los visitantes escanean, no leen. El patrón de acordeón resuelve esto ocultando las respuestas detrás de preguntas que se pueden tocar, de modo que cada usuario encuentra justo lo que necesita.
Esta guía cubre 8 patrones de acordeón FAQ en React con Tailwind CSS, desde una implementación mínima hasta diseños animados, por categorías y a varias columnas.
1. El acordeón nativo de HTML
Antes de recurrir a JavaScript, considera los elementos integrados <details> y <summary>. Son accesibles desde el primer momento, no requieren gestión de estado y funcionan incluso con JavaScript desactivado:
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">
Frequently Asked Questions
</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 clase group-open:rotate-45 gira el signo más cuando el detalle está abierto, convirtiéndolo en una forma de x. CSS puro, cero JavaScript.
2. Acordeón controlado con useState
Cuando necesitas que solo haya un elemento abierto a la vez (un patrón de UX habitual), necesitas estado:
"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>
);
}
El atributo aria-expanded indica a las tecnologías de asistencia si el contenido está visible. Resulta esencial para la accesibilidad, así que no lo omitas.
3. Acordeón animado con Framer Motion
El acordeón controlado anterior tiene un problema: la respuesta aparece y desaparece de golpe. Añadir una animación de altura con Framer Motion hace que la interacción se sienta más cuidada:
import { AnimatePresence, motion } from "framer-motion";
// Inside the 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>
)}
La animación de height: "auto" es una de las mejores funciones de Framer Motion. CSS no puede animar de forma nativa hacia una altura auto: necesitarías JavaScript para medir antes el elemento. Framer Motion lo gestiona internamente.
4. Acordeón con varios elementos abiertos
Algunas secciones FAQ funcionan mejor cuando se pueden abrir varios elementos a la vez. Sustituye el openIndex único por 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;
});
}
Usa este patrón cuando los elementos del FAQ sean cortos y los usuarios quizá quieran comparar respuestas entre distintas preguntas.
5. FAQ a dos columnas
Para listas FAQ más largas (12 o más preguntas), una sola columna genera un scroll intimidante. Reparte los elementos en dos columnas con 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>
Los elementos fluyen de izquierda a derecha y de arriba abajo: el 1 y el 2 comparten la primera fila, el 3 y el 4 la segunda, y así sucesivamente. Esto reduce a la mitad la longitud percibida de la sección.
6. FAQ con categorías
Las páginas FAQ de producto suelen abarcar varios temas: Facturación, Funciones, Seguridad, Cuenta. Agrupa las preguntas bajo encabezados de categoría y permite que los usuarios filtren:
const categories = ["All", "Billing", "Features", "Security", "Account"];
function CategorizedFAQ({ items }: { items: (FAQItem & { category: string })[] }) {
const [activeCategory, setActiveCategory] = useState("All");
const filtered = activeCategory === "All"
? 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>
);
}
El selector de categorías estilo "píldora" invita más que un menú desplegable. Los usuarios ven todas las categorías de un vistazo y cambian al instante.
7. FAQ con búsqueda
Para bases de conocimiento con más de 50 preguntas, añade un campo de búsqueda encima del acordeón. Filtra los elementos del lado del cliente con una simple comprobación includes sobre el texto de la pregunta y de la respuesta:
const [query, setQuery] = useState("");
const filtered = items.filter(
(item) =>
item.question.toLowerCase().includes(query.toLowerCase()) ||
item.answer.toLowerCase().includes(query.toLowerCase())
);
Muestra un mensaje de "No se han encontrado resultados" cuando la lista filtrada quede vacía. Evita esconder el campo de búsqueda detrás de un botón: si el FAQ es lo bastante largo como para justificar una búsqueda, el campo debería estar siempre visible.
8. FAQ con marcado de Schema
Google puede mostrar el contenido de un FAQ directamente en los resultados de búsqueda como fragmentos enriquecidos. Añade datos estructurados JSON-LD a tu sección 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,
},
})),
};
return (
<script
type="application/ld+json"
// Use a JSON-LD serialization library or sanitize
// the content before rendering in production
/>
);
}
Coloca este componente junto a tu sección FAQ. Los datos estructurados no afectan al renderizado visual, pero indican a los motores de búsqueda que tu página contiene contenido FAQ apto para resultados enriquecidos.
Lista de comprobación de accesibilidad
- Usa elementos
<button>como disparadores del acordeón, no<div>cononClick - Incluye
aria-expandeden cada disparador - Usa
aria-controlspara vincular el botón con eliddel panel - Asegúrate de que la navegación por teclado funcione: Enter y Espacio deberían alternar los elementos
- Mantén indicadores de foco visibles en los disparadores
Secciones FAQ listas para usar
Construir un FAQ accesible y animado desde cero lleva un tiempo que podrías dedicar a tu producto real. El catálogo FAQ de Incubator tiene más de 10 secciones FAQ listas para usar: acordeones, a dos columnas, por categorías, con búsqueda, con marcado de Schema, todas construidas en React y Tailwind CSS.
Explora la biblioteca de componentes completa para cada sección que necesite tu landing page, desde heroes hasta precios.
Relacionado en incubator
- Componentes FAQ en React: secciones de acordeón y FAQ listas para usar.
- Catálogo completo de componentes: explora las 449 secciones para copiar y pegar.
- Componentes CTA en React: secciones de llamada a la acción orientadas a la conversión.
- Efectos de animación al hacer scroll: secciones FAQ con animaciones de entrada.