FAQ Section
Frequently asked questions with accordion interface
FAQ Section
The <FAQ> section displays frequently asked questions in an accordion, grid, or list layout. It addresses common concerns and objections before users contact support or abandon the page.
Interface
import type { FAQProps } from 'mdxui'
interface FAQProps {
/** Section title */
title?: string
/** FAQ items */
items: FAQItem[]
/** Layout variant */
variant?: 'accordion' | 'grid' | 'list'
}
interface FAQItem {
/** Question */
question: string
/** Answer */
answer: string
}Usage
Basic Example
import { FAQ } from '@mdxui/beacon'
<FAQ
items={[
{
question: "How does the free trial work?",
answer: "Start with a 14-day free trial. No credit card required. Cancel anytime."
},
{
question: "Can I change plans later?",
answer: "Yes, you can upgrade or downgrade your plan at any time from your account settings."
}
]}
/>With Title
<FAQ
title="Frequently Asked Questions"
items={[
{
question: "What payment methods do you accept?",
answer: "We accept all major credit cards (Visa, MasterCard, Amex) and PayPal."
},
{
question: "Is there a setup fee?",
answer: "No setup fees. You only pay for your monthly or annual subscription."
}
]}
/>Accordion Variant (Default)
<FAQ
variant="accordion"
title="Common questions"
items={[
{
question: "How secure is my data?",
answer: "We use bank-level 256-bit encryption and are SOC 2 Type II certified. Your data is encrypted at rest and in transit."
},
{
question: "Do you offer refunds?",
answer: "Yes, we offer a 30-day money-back guarantee. If you're not satisfied, contact us for a full refund."
},
{
question: "Can I export my data?",
answer: "Absolutely. You can export all your data in CSV or JSON format at any time from your dashboard."
}
]}
/>Grid Variant
<FAQ
variant="grid"
title="Got questions? We've got answers"
items={[
{
question: "Is mdxui open source?",
answer: "Yes, mdxui is MIT licensed and available on GitHub."
},
{
question: "Does it work with Next.js?",
answer: "Yes, mdxui works with Next.js, Remix, Vite, and any React framework."
},
{
question: "Do I need Payload CMS?",
answer: "No, the components work standalone. Payload CMS integration is optional."
},
{
question: "What about TypeScript?",
answer: "Full TypeScript support with Zod schemas for runtime validation."
}
]}
/>List Variant
<FAQ
variant="list"
items={[
{
question: "What is your uptime SLA?",
answer: "We guarantee 99.9% uptime. Our infrastructure is hosted on AWS with multi-region redundancy and automatic failover."
},
{
question: "How does billing work?",
answer: "You're billed monthly or annually based on your plan. All billing is automatic and you can update your payment method or cancel anytime."
}
]}
/>Props
title (optional)
Type: string
Section heading displayed above the FAQ items.
<FAQ
title="Frequently Asked Questions"
items={[...]}
/>items (required)
Type: FAQItem[]
Array of question and answer pairs.
interface FAQItem {
question: string // The question
answer: string // The answer
}
<FAQ
items={[
{
question: "How do I get started?",
answer: "Sign up for a free account and follow the onboarding wizard."
}
]}
/>variant (optional)
Type: 'accordion' | 'grid' | 'list'
Default: 'accordion'
Controls the layout and interaction style.
// Accordion: Expandable/collapsible (interactive)
<FAQ variant="accordion" />
// Grid: All answers visible in a grid
<FAQ variant="grid" />
// List: All answers visible in a vertical list
<FAQ variant="list" />FAQ Item Props
question (required)
Type: string
The question being asked. Should be clear and concise.
{
question: "What happens after my trial ends?"
}Best practices:
- Use question format (who, what, when, where, why, how)
- Keep it under 100 characters
- Be specific
- Use customer language
answer (required)
Type: string
The answer to the question. Can be longer and more detailed.
{
answer: "After your 14-day trial, you'll be prompted to choose a paid plan. You can select from our Starter, Pro, or Enterprise tiers based on your needs."
}Best practices:
- Be complete but concise
- Use 1-3 sentences
- Include relevant links if needed
- Be honest and transparent
Variants in Detail
Accordion Variant
Interactive expandable/collapsible sections.
<FAQ
variant="accordion"
title="Support"
items={[
{
question: "How do I contact support?",
answer: "Email us at support@example.com or use the chat widget in the bottom right corner. We typically respond within 2 hours during business hours."
},
{
question: "Do you offer phone support?",
answer: "Phone support is available for Pro and Enterprise plans. Schedule a call from your dashboard."
},
{
question: "What are your support hours?",
answer: "Email and chat support: 9am-6pm EST, Monday-Friday. Enterprise customers get 24/7 support."
}
]}
/>Best for:
- Many questions (6+)
- Longer answers
- Scannable questions
Features:
- One open at a time (accordion behavior)
- Smooth expand/collapse animation
- Keyboard navigation
- Mobile-friendly
Grid Variant
All questions and answers visible in a card grid.
<FAQ
variant="grid"
title="Quick answers"
items={[
{
question: "Is it free?",
answer: "Free 14-day trial, then starting at $29/mo."
},
{
question: "Mobile app?",
answer: "iOS and Android apps available."
},
{
question: "Integrations?",
answer: "Integrates with Slack, Zapier, and 50+ tools."
},
{
question: "Team features?",
answer: "Real-time collaboration, comments, and permissions."
}
]}
/>Best for:
- Short answers (1 sentence)
- Quick scanning
- 4-8 questions
List Variant
All questions and answers visible in a vertical list.
<FAQ
variant="list"
title="Billing questions"
items={[
{
question: "When will I be charged?",
answer: "You'll be charged at the end of your trial period, then monthly or annually depending on your plan selection."
},
{
question: "Can I get an invoice?",
answer: "Yes, invoices are automatically sent to your email after each payment. You can also download them from your billing page."
},
{
question: "What currency do you charge in?",
answer: "All prices are in USD. International cards are accepted and your bank will handle the currency conversion."
}
]}
/>Best for:
- Detailed answers
- Few questions (3-5)
- Print-friendly
Implementation Details
The reference implementation from @mdxui/beacon includes:
Features
- Accordion Animation - Smooth expand/collapse
- Keyboard Navigation - Arrow keys to navigate
- Accessible - ARIA labels and semantic HTML
- Responsive - Mobile-optimized
- Single Open - Only one accordion item open at a time
- Deep Linking - Link to specific questions (optional)
Structure
<section className="faq">
{title && (
<div className="faq-header">
<h2>{title}</h2>
</div>
)}
{variant === 'accordion' && (
<div className="faq-accordion">
{items.map((item, index) => (
<details key={index} className="faq-item">
<summary className="question">
{item.question}
<ChevronDown className="icon" />
</summary>
<div className="answer">
<p>{item.answer}</p>
</div>
</details>
))}
</div>
)}
{variant === 'grid' && (
<div className="faq-grid">
{items.map((item, index) => (
<div key={index} className="faq-item">
<h3 className="question">{item.question}</h3>
<p className="answer">{item.answer}</p>
</div>
))}
</div>
)}
{variant === 'list' && (
<dl className="faq-list">
{items.map((item, index) => (
<div key={index} className="faq-item">
<dt className="question">{item.question}</dt>
<dd className="answer">{item.answer}</dd>
</div>
))}
</dl>
)}
</section>Real-World Examples
SaaS Product FAQ
<FAQ
title="Frequently Asked Questions"
variant="accordion"
items={[
{
question: "How does the free trial work?",
answer: "Sign up for a 14-day free trial with full access to all features. No credit card required. At the end of the trial, choose a plan or your account will be downgraded to the free tier."
},
{
question: "Can I cancel anytime?",
answer: "Yes, you can cancel your subscription at any time. Your account will remain active until the end of your billing period."
},
{
question: "What happens to my data if I cancel?",
answer: "Your data remains accessible for 30 days after cancellation. You can export everything during this time. After 30 days, data is permanently deleted."
},
{
question: "Do you offer discounts for nonprofits or education?",
answer: "Yes! We offer 50% off for verified nonprofits and educational institutions. Contact sales@example.com with your documentation."
},
{
question: "Is my data secure?",
answer: "Absolutely. We use bank-level encryption, are SOC 2 Type II certified, and GDPR compliant. Your data is encrypted at rest and in transit."
},
{
question: "Can I upgrade or downgrade my plan?",
answer: "Yes, change plans anytime from your account settings. Upgrades take effect immediately. Downgrades take effect at the end of your current billing period."
}
]}
/>Developer Platform FAQ
<FAQ
title="Developer FAQ"
variant="grid"
items={[
{
question: "What languages do you support?",
answer: "JavaScript, TypeScript, Python, Ruby, Go, and PHP."
},
{
question: "Is there a CLI?",
answer: "Yes, install via npm: npm install -g @example/cli"
},
{
question: "Do you have webhooks?",
answer: "Yes, configure webhooks in your dashboard to receive real-time events."
},
{
question: "What's the API rate limit?",
answer: "1000 requests/hour on Free, 10k/hour on Pro, unlimited on Enterprise."
},
{
question: "Do you support GraphQL?",
answer: "Yes, we offer both REST and GraphQL APIs."
},
{
question: "Where's the documentation?",
answer: "Full docs at docs.example.com with examples in all supported languages."
}
]}
/>E-commerce FAQ
<FAQ
title="Shopping FAQ"
variant="list"
items={[
{
question: "What is your return policy?",
answer: "We accept returns within 30 days of purchase for a full refund. Items must be unused and in original packaging. Free return shipping on all orders."
},
{
question: "How long does shipping take?",
answer: "Standard shipping: 5-7 business days. Express shipping: 2-3 business days. Free shipping on orders over $50."
},
{
question: "Do you ship internationally?",
answer: "Yes, we ship to over 100 countries. International orders typically arrive in 10-14 business days. Customs fees may apply."
},
{
question: "How can I track my order?",
answer: "You'll receive a tracking number via email once your order ships. Track your package at any time from your account dashboard."
}
]}
/>Accordion Behavior
Single vs Multiple Open
Most implementations allow only one item open at a time:
const [openIndex, setOpenIndex] = useState<number | null>(null)
const toggle = (index: number) => {
setOpenIndex(openIndex === index ? null : index)
}For multiple open items:
const [openIndexes, setOpenIndexes] = useState<Set<number>>(new Set())
const toggle = (index: number) => {
const newSet = new Set(openIndexes)
if (newSet.has(index)) {
newSet.delete(index)
} else {
newSet.add(index)
}
setOpenIndexes(newSet)
}Using HTML Details
The simplest implementation uses native <details>:
<details>
<summary>{question}</summary>
<div>{answer}</div>
</details>Pros:
- Native HTML
- No JavaScript required
- Accessible by default
Cons:
- Limited styling control
- Can't enforce single-open
- Animation support varies
Accessibility
The reference implementation includes:
- Semantic HTML - Uses
<section>,<details>,<dl>,<dt>,<dd> - ARIA Labels - Proper labels for interactive elements
- Keyboard Navigation - Tab, Enter, Space keys work
- Focus Management - Visible focus indicators
- Screen Readers - Announces expanded/collapsed state
<section className="faq" aria-labelledby="faq-title">
<h2 id="faq-title">{title}</h2>
{/* Accordion variant */}
<div className="accordion" role="region">
{items.map((item, index) => (
<div className="accordion-item" key={index}>
<h3>
<button
aria-expanded={openIndex === index}
aria-controls={`faq-answer-${index}`}
id={`faq-question-${index}`}
onClick={() => toggle(index)}
>
{item.question}
</button>
</h3>
<div
id={`faq-answer-${index}`}
aria-labelledby={`faq-question-${index}`}
hidden={openIndex !== index}
>
<p>{item.answer}</p>
</div>
</div>
))}
</div>
{/* List variant using definition list */}
<dl className="faq-list">
{items.map((item, index) => (
<div key={index}>
<dt>{item.question}</dt>
<dd>{item.answer}</dd>
</div>
))}
</dl>
</section>Best Practices
Choosing Questions
- Use real questions: From customers, support tickets
- Address objections: Price, features, security
- Be comprehensive: Cover full customer journey
- Update regularly: Add new questions as they arise
- Prioritize: Most important/common first
Writing Questions
- Use customer language: How they ask, not how you'd say it
- Be specific: "How does billing work?" > "Billing?"
- Keep it short: Under 100 characters
- Use keywords: For SEO and search
Writing Answers
- Be complete: Answer fully, don't make them ask follow-ups
- Be concise: 1-3 sentences ideal
- Be honest: Don't oversell or hide limitations
- Add links: Link to detailed docs if needed
- Use formatting: Bold key points if answer is long
Organization
- Group by topic: Billing, Features, Support, etc.
- Order by importance: Most common questions first
- Limit total: 6-12 questions ideal, more needs search
- Consider multiple sections: Break up long FAQs
Rich Content in Answers
For answers with formatting, links, or lists:
interface FAQItem {
question: string
answer: string | ReactNode // Allow React elements
}
<FAQ
items={[
{
question: "What payment methods do you accept?",
answer: (
<>
<p>We accept:</p>
<ul>
<li>Credit cards (Visa, MasterCard, Amex)</li>
<li>PayPal</li>
<li>ACH bank transfer (Enterprise only)</li>
</ul>
<p>Learn more in our <a href="/billing">billing docs</a>.</p>
</>
)
}
]}
/>Search Integration
For large FAQs, add search functionality:
const [searchTerm, setSearchTerm] = useState('')
const filteredItems = items.filter(item =>
item.question.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.answer.toLowerCase().includes(searchTerm.toLowerCase())
)
<div className="faq">
<input
type="search"
placeholder="Search FAQs..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<FAQ items={filteredItems} />
</div>Composition
In LandingPage
FAQ typically appears near the end, before final CTA:
<LandingPage>
<Hero {...heroProps} />
<Features {...featuresProps} />
<Pricing {...pricingProps} />
<Testimonials {...testimonialsProps} />
<FAQ {...faqProps} />
<CTA {...ctaProps} />
</LandingPage>With CTA
End with CTA for users with remaining questions:
<FAQ {...faqProps} />
<CTA
title="Still have questions?"
description="Our team is here to help"
cta={{ text: "Contact Support", href: "/contact" }}
/>Multiple FAQ Sections
Break up by category:
<FAQ
title="Product Questions"
items={productQuestions}
/>
<FAQ
title="Billing Questions"
items={billingQuestions}
/>
<FAQ
title="Technical Questions"
items={technicalQuestions}
/>TypeScript
Full type definitions:
import { z } from 'zod'
import type { Component } from 'mdxui'
export const FAQItemSchema = z.object({
question: z.string(),
answer: z.string(),
})
export const FAQPropsSchema = z.object({
title: z.string().optional(),
items: z.array(FAQItemSchema),
variant: z.enum(['accordion', 'grid', 'list']).default('accordion'),
})
export type FAQItem = z.infer<typeof FAQItemSchema>
export type FAQProps = z.infer<typeof FAQPropsSchema>
export type FAQ = Component<FAQProps>Related Components
- Pricing Section - Answer pricing questions
- CTA Section - Convert after addressing concerns
- Testimonials Section - Build trust before FAQ
- Accordion - Base accordion component