---
title: FAQ
description: An accordion of questions that emits FAQPage structured data derived from what it rendered.
category: Marketing
related:
  - /components/accordion
  - /components/collapsible
  - /components/structured-data
  - /components/testimonial
visibility: guest
draft: false
---

# FAQ

**FAQ** stands for "frequently asked questions" — the list of things people keep
asking, answered once in the place they will look.

Structurally it is an accordion, and WireKit already has a good one, so this
component uses it rather than growing a second. What it adds is the part an accordion cannot
know: that these rows are **questions**, and that search engines should be told
so.

::: info FAQ vs Accordion vs Collapsible — when to use which

- **FAQ** (this component) — a question list built on the accordion that also emits [FAQPage](/components/structured-data) structured data derived from what it renders.
- **[Accordion](/components/accordion)** — the general *coordinated group* of panels (single or multiple open, card chrome) with no question semantics or schema.
- **[Collapsible](/components/collapsible)** — a *single* independent show/hide, or a stack of them with no coordination.
:::

`<x-wirekit::faq>` emits [FAQPage](https://schema.org/FAQPage) structured data
built from the questions it just rendered — from the *visible* answer, never a
second hand-written copy. Normally that duplication is your problem: you write the
accordion, then you write the JSON-LD again, and the two drift apart on the first
copy edit.

Here they cannot drift, because there is only one copy. Reword a question and the
schema follows. Delete one and it leaves the schema too. The schema is made out
of the visible answer rather than a copy of it.

:::preview{title="FAQ with structured data"}
<x-wirekit::faq label="Frequently asked questions" style="width: 34rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::faq-item question="How do I install WireKit?">
        Require the package with Composer, then include the style and script
        directives in your layout. No build step and no configuration file.
    </x-wirekit::faq-item>
    <x-wirekit::faq-item question="Is WireKit free to use commercially?">
        Yes. WireKit is MIT licensed, so you can use it in commercial products
        without a fee and without asking.
    </x-wirekit::faq-item>
    <x-wirekit::faq-item question="Do I need to write any JavaScript?">
        No. Every interactive component ships its own behavior, and you compose
        them from Blade like any other tag.
    </x-wirekit::faq-item>
</x-wirekit::faq>
:::

:::source{language="blade"}
<x-wirekit::faq label="Frequently asked questions">
    <x-wirekit::faq-item question="How do I install WireKit?">
        Require the package with Composer, then include the style and script
        directives in your layout. No build step and no configuration file.
    </x-wirekit::faq-item>
    <x-wirekit::faq-item question="Is WireKit free to use commercially?">
        Yes. WireKit is MIT licensed, so you can use it in commercial products
        without a fee and without asking.
    </x-wirekit::faq-item>
    <x-wirekit::faq-item question="Do I need to write any JavaScript?">
        No. Every interactive component ships its own behavior, and you compose
        them from Blade like any other tag.
    </x-wirekit::faq-item>
</x-wirekit::faq>
:::

Look at the page source of that preview and you will find a
`<script type="application/ld+json">` block listing exactly those three
questions — no second list to maintain.

## One FAQPage per page

A page should carry **exactly one** `FAQPage`. Two of them compete rather than
combine, and search engines may ignore both.

So if you show more than one FAQ block on a page, let one of them own the schema
and turn it off on the others:

```blade
{{-- The main FAQ owns the schema --}}
<x-wirekit::faq>
    <x-wirekit::faq-item question="How do I install it?">…</x-wirekit::faq-item>
</x-wirekit::faq>

{{-- A second block on the same page: same questions, no competing schema --}}
<x-wirekit::faq :schema="false">
    <x-wirekit::faq-item question="Billing question">…</x-wirekit::faq-item>
</x-wirekit::faq>
```

Turning the schema off never changes what the reader sees — only what search
engines are told.

::: warning Do not describe answers you are not showing
Only emit the schema where the questions and answers are really on the page. An
FAQPage describing content the reader cannot reach is a search-engine policy
violation, not a shortcut to rich results. This component keeps you on the right
side of that by construction — but if you hide the questions behind something the
crawler cannot open, turn `schema` off.
:::

## Rich answers

An answer is a slot, so it takes real markup — links, lists, emphasis:

```blade
<x-wirekit::faq-item question="Where are the docs?">
    See <a href="https://docs.wirekit.app">the documentation</a>, or run
    <code>php artisan wirekit:list</code> to browse every component from your terminal.
</x-wirekit::faq-item>
```

The same markup goes into the schema, because the schema *is* the rendered answer.

### When the markup is too rich for the schema

Search engines accept only a limited HTML subset inside a FAQPage answer. An
answer built from nested components — anything that renders `data-*` attributes,
Alpine directives, or utility classes — can therefore fail rich-result validation
even though it reads perfectly on the page.

`plain-text` strips the markup **from the schema only**. The reader still sees the
full rendered answer; the schema gets the same words as text:

```blade
<x-wirekit::faq plain-text>
    <x-wirekit::faq-item question="What is included?">
        <x-wirekit::badge>New</x-wirekit::badge> Priority support
    </x-wirekit::faq-item>
</x-wirekit::faq>
```

The schema answer becomes `Priority support` prefixed by the badge's text —
entities decoded, whitespace collapsed. It stays **derived** from what you
rendered, so the indexed answer can never drift away from the visible one.

For the rare case where the schema answer must differ in *content* rather than
markup, a single item can override it outright:

```blade
<x-wirekit::faq-item question="How do refunds work?" schema-answer="Refunds are issued within 14 days.">
    …the long, linked, formatted answer the reader sees…
</x-wirekit::faq-item>
```

Reach for `schema-answer` sparingly: it is hand-written copy, and hand-written
copy is what drifts.

## Appearance

`faq` defaults to the accordion's `flush` variant and `lg` size, because an FAQ
almost always sits inline in page content where the surrounding section already
draws the chrome — a second border there only competes with it. Pass `variant`
or `size` to override; they go straight through to the accordion. Use the boolean
`multiple` prop to let more than one entry stay open at once (faq exposes it
instead of the accordion's `mode` string).

```blade
<x-wirekit::faq variant="separated" size="md" multiple>
    …
</x-wirekit::faq>
```

## Props

### `<x-wirekit::faq>`

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | `string` | `'Frequently asked questions'` | Accessible name for the question list. |
| `variant` | `string` | `'flush'` | Passed to the accordion: `flush`, `bordered`, `separated`. |
| `size` | `string` | `'lg'` | Passed to the accordion: `md`, `lg`. |
| `multiple` | `bool` | `false` | Let several answers stand open at once. Default is one at a time. |
| `schema` | `bool` | `true` | Emit FAQPage JSON-LD for the questions rendered here. |
| `plainText` | `bool` | `false` | Strip markup from the **schema** answer text (entities decoded, whitespace collapsed). The visible answer is untouched. |
| `scope` | `string\|null` | `null` | Class-scope override. |

### `<x-wirekit::faq-item>`

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `question` | `string` | `''` | The question. Rendered as the trigger and recorded for the schema. |
| `schemaAnswer` | `string\|null` | `null` | Replace this item's schema answer outright, for the rare case where it must differ from the visible one in content. Prefer `plain-text` on the faq. |
| `id` | `string\|null` | `null` | Explicit item id. Generated when omitted. |
| `scope` | `string\|null` | `null` | Class-scope override. |

## Accessibility

The accordion's accessibility is inherited whole, not reimplemented: each question
is a real `<button>` inside a heading with `aria-expanded` and `aria-controls`, and
each answer is a `role="region"` labeled by its question. Keyboard support comes
from the same place.

An empty question or an empty answer is dropped from the schema rather than
emitted as a node with no name — a `Question` with nothing to ask is not
something to tell a search engine about.

## Keyboard Interaction

Inherited from [accordion](/components/accordion) — `Tab` moves between questions,
`Enter` / `Space` opens and closes the focused one.

## Structured data & AI search (GEO)

::: info Who reads this schema now — LLMs and AI answer engines
The `FAQPage` markup is consumed mostly by **LLM-based answer engines and AI
crawlers** today — the AI agents and AI search overviews that read structured Q&A to
extract and cite answers. That is *generative engine optimization* (GEO), and FAQ is
among the schema types those engines cite most often; being
machine-readable, it reaches an AI agent that only sees your HTML and cannot run the
JavaScript that reveals the accordion. Google's own FAQ **rich result**, by contrast,
is retired — narrowed to authoritative government and health sites in 2023, then
removed in 2026 — so the payoff today is visibility in AI answers plus a schema built
from the visible answer, not a Google rich result.
:::
