# FAQ Page

> Create a frequently asked questions page to address common user queries.

*Canonical: https://makerkit.dev/docs/tanstack-drizzle/marketing-pages/faq*

---

The FAQ page helps reduce support load by answering common questions. The kit provides a ready-to-use FAQ page with SEO-optimized structured data.

## File Location

The FAQ page is located at `apps/web/src/routes/_public/faq.tsx`.

## Page Structure

The FAQ page uses:
- `SitePageHeader` for the page title and subtitle
- Native HTML `<details>` and `<summary>` elements for the accordion
- JSON-LD structured data for SEO

## FAQ Item Component

Each FAQ item uses the native `<details>` element for accessibility and no JavaScript requirement:

```tsx
function FaqItem({
  item,
}: {
  item: {
    question: string;
    answer: string;
  };
}) {
  return (
    <details
      className="hover:bg-muted/70 [&:open]:bg-muted/70 transition-all"
    >
      <summary className="flex items-center justify-between p-4 hover:cursor-pointer">
        <h2 className="cursor-pointer font-sans text-base">
          {item.question}
        </h2>
        <ChevronDown className="h-5 transition duration-300 group-open:-rotate-180" />
      </summary>

      <div className="text-muted-foreground flex flex-col gap-y-2 px-4 pb-2">
        {item.answer}
      </div>
    </details>
  );
}
```

## Translations

FAQ content is stored in translation files for internationalization. Add your questions and answers to the translation file `packages/i18n/src/messages/en/marketing.json`.

```json
{
  "faq": "FAQ",
  "faqSubtitle": "Frequently asked questions about the platform",
  "contactFaq": "If you have any questions, please contact us",
  "faqQuestion1": "Do you offer a free trial?",
  "faqAnswer1": "Yes, we offer a 14-day free trial. You can cancel at any time during the trial period and you won't be charged.",
  "faqQuestion2": "Can I cancel my subscription?",
  "faqAnswer2": "You can cancel your subscription at any time. You can do this from your account settings.",
  "faqQuestion3": "Where can I find my invoices?",
  "faqAnswer3": "You can find your invoices in your account settings.",
  "faqQuestion4": "What payment methods do you accept?",
  "faqAnswer4": "We accept all major credit cards and PayPal.",
  "faqQuestion5": "Can I upgrade or downgrade my plan?",
  "faqAnswer5": "Yes, you can upgrade or downgrade your plan at any time. You can do this from your account settings.",
  "faqQuestion6": "Do you offer discounts for non-profits?",
  "faqAnswer6": "Yes, we offer a 50% discount for non-profits. Please contact us to learn more."
}
```

## Adding FAQ Items

To add more FAQ items:

Add the question and answer translations to `marketing.json`:

```json
{
  "faqQuestion7": "Your new question?",
  "faqAnswer7": "Your answer here."
}
```

Add the item to the `faqItems` array in the page:

```tsx
const faqItems = [
  // ... existing items
  {
    question: t('faqQuestion7'),
    answer: t('faqAnswer7'),
  },
];
```

## SEO Structured Data

The page includes JSON-LD structured data for better search engine visibility. This helps search engines display FAQ rich results:

```tsx
const structuredData = {
  '@context': 'https://schema.org',
  '@type': 'FAQPage',
  mainEntity: faqItems.map((item) => ({
    '@type': 'Question',
    name: item.question,
    acceptedAnswer: {
      '@type': 'Answer',
      text: item.answer,
    },
  })),
};
```

The structured data is rendered as a script tag:

```tsx
<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
```

## Page Metadata

The document title and description are set in the route's `head()`. Outside of React render, build a translator with `getTranslator` from `#/lib/i18n/translator.ts`:

```tsx
import { getTranslator } from '#/lib/i18n/translator.ts';

export const Route = createFileRoute('/_public/faq')({
  head: () => {
    const t = getTranslator();

    return {
      meta: [
        { title: t('marketing.faq') },
        { name: 'description', content: t('marketing.faqSubtitle') },
      ],
    };
  },
  component: FAQPage,
});
```

Inside the component, read the translations with `useTranslations` from `use-intl`:

```tsx
import { useTranslations } from 'use-intl';

const t = useTranslations('marketing');
```

## SitePageHeader Component

The `SitePageHeader` component provides a consistent header style across marketing pages:

```tsx
import { SitePageHeader } from '#/components/site/site-page-header.tsx';

<SitePageHeader
  title={t('faq')}
  subtitle={t('faqSubtitle')}
/>
```

This component is located at:

```
apps/web/src/components/site/site-page-header.tsx
```

## Customization

### Styling the Accordion

Modify the `FaqItem` component to change the accordion appearance:

```tsx
<details className="hover:bg-muted/70 [&:open]:bg-muted/70 transition-all">
  {/* Customize hover and open states */}
</details>
```

### Grouping by Category

To group FAQs by category, restructure the data:

```tsx
const faqCategories = [
  {
    category: 'Billing',
    items: [
      { question: t('faqQuestion1'), answer: t('faqAnswer1') },
      { question: t('faqQuestion2'), answer: t('faqAnswer2') },
    ],
  },
  {
    category: 'Account',
    items: [
      { question: t('faqQuestion3'), answer: t('faqAnswer3') },
    ],
  },
];
```

Then render with category headers:

```tsx
{faqCategories.map((category) => (
  <div key={category.category}>
    <h2 className="text-xl font-semibold mb-4">{category.category}</h2>
    <div className="divide-y divide-dashed rounded-md border">
      {category.items.map((item, index) => (
        <FaqItem key={index} item={item} />
      ))}
    </div>
  </div>
))}
```
