# FAQ Accordion

> A centred column of expandable question cards on native details, with FAQPage structured data.

- **Slug:** `faq-accordion`
- **Kind:** section · **Category:** Conversion
- **Source:** original to Viewership UI
- **Docs page:** https://viewership.ai/design/components/faq-accordion
- **All files in one bundle:** https://viewership.ai/design/components/faq-accordion.bundle.txt
- **Structured JSON:** https://viewership.ai/design/components/faq-accordion.json

## Install

1. Import the foundation tokens once (download: https://viewership.ai/design/raw/foundation.css.txt):

```astro
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
---
```

2. Copy this file into `src/components/viewership/faq-accordion/`:

   - `FaqAccordion.astro` (raw: https://viewership.ai/design/raw/faq-accordion/FaqAccordion.astro.txt)

npm packages: none.

## Usage

```astro
---
import FaqAccordion from "../components/viewership/faq-accordion/FaqAccordion.astro";
---

<FaqAccordion />
```

## Example

The gallery demo, verbatim. Use the import path from Usage instead of `./`.

```astro
---
import FaqAccordion from "./FaqAccordion.astro";
---

<FaqAccordion
  title="Questions,"
  accent="answered."
  schema={false}
  items={[
    { q: "What does the repair promise cover?", a: "Stitching, zips, seam tape and fabric patches on anything we made, for as long as you own it." },
    { q: "How long does a repair take?", a: "Two working days on average once the pack reaches the bench, plus shipping both ways." },
    { q: "Do you ship outside the United States?", a: "Yes, to Canada and the UK today. Duties are shown at checkout." },
    { q: "Can I return something I have used?", a: "Within 60 days, yes. Tell us what did not work and we will take it back." },
  ]}
/>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` |  | Section headline. |
| `accent` | `string` |  | Closing words of the headline. |
| `intro` | `string` |  | Supporting paragraph. |
| `items` | `{ q: string; a: string }[]` |  | Questions and plain-text answers. |
| `openFirst` | `boolean` | `true` | Open the first question on load. |
| `schema` | `boolean` | `true` | Emit FAQPage JSON-LD. |
| `as` | `"h2" \| "h3"` | `"h2"` | Heading element. |

## Source

### `src/components/viewership/faq-accordion/FaqAccordion.astro`

```astro
---
/**
 * FaqAccordion: one centred column of expandable questions on native
 * <details>, with FAQPage JSON-LD included. Works without JavaScript.
 * Set `schema={false}` if the page already emits FAQPage markup.
 *
 * @example
 * <FaqAccordion title="Questions," accent="answered." items={[{ q: "Do you ship abroad?", a: "Yes." }]} />
 */
interface Item {
  q: string;
  /** Plain text. Also used verbatim in the structured data. */
  a: string;
}
interface Props {
  title: string;
  accent?: string;
  intro?: string;
  items: Item[];
  openFirst?: boolean;
  schema?: boolean;
  as?: "h2" | "h3";
  class?: string;
}

const { title, accent, intro, items, openFirst = true, schema = true, as: Heading = "h2", class: className } = Astro.props;
const jsonLd = JSON.stringify({
  "@context": "https://schema.org",
  "@type": "FAQPage",
  mainEntity: items.map((i) => ({ "@type": "Question", name: i.q, acceptedAnswer: { "@type": "Answer", text: i.a } })),
}).replace(/</g, "\\u003c");
---

<section class:list={["vs-faq-accordion", className]}>
  <div class="vs-faq-accordion__inner">
    <header class="vs-faq-accordion__head">
      <Heading class="vs-display vs-faq-accordion__title">{title}{accent && <> <em>{accent}</em></>}</Heading>
      {intro && <p class="vs-faq-accordion__intro">{intro}</p>}
    </header>
    <div class="vs-faq-accordion__list">
      {
        items.map((item, i) => (
          <details class="vs-faq-accordion__row" open={openFirst && i === 0}>
            <summary>
              <span class="vs-faq-accordion__q">{item.q}</span>
              <span class="vs-faq-accordion__mark" aria-hidden="true" />
            </summary>
            <p class="vs-faq-accordion__a">{item.a}</p>
          </details>
        ))
      }
    </div>
  </div>
  {schema && <script is:inline type="application/ld+json" set:html={jsonLd} />}
</section>

<style>
  .vs-faq-accordion {
    container-type: inline-size;
    background: var(--vs-page);
    color: var(--vs-ink);
    font-family: var(--vs-font-sans);
    font-stretch: var(--vs-font-stretch);
  }
  .vs-faq-accordion__inner {
    max-width: 52rem;
    margin-inline: auto;
    padding: var(--vs-section-y) var(--vs-gutter);
  }
  .vs-faq-accordion__head {
    text-align: center;
  }
  .vs-faq-accordion__title {
    font-size: clamp(2rem, 6.5cqi, 4.25rem);
  }
  .vs-faq-accordion__intro {
    max-width: 32rem;
    margin: 1.25rem auto 0;
    font-size: 1.0625rem;
    line-height: 1.55;
    color: var(--vs-ink-2);
  }
  .vs-faq-accordion__list {
    display: grid;
    gap: 10px;
    margin-top: clamp(28px, 5cqi, 56px);
  }
  .vs-faq-accordion__row {
    border: var(--vs-border-w) solid var(--vs-line);
    border-radius: var(--vs-radius-card);
    background: var(--vs-surface);
    transition: box-shadow var(--vs-duration) var(--vs-ease-out);
  }
  .vs-faq-accordion__row[open] {
    box-shadow: var(--vs-shadow-md);
  }
  .vs-faq-accordion__row summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    padding: 18px 22px;
    border-radius: inherit;
    cursor: pointer;
    list-style: none;
  }
  .vs-faq-accordion__row summary::-webkit-details-marker {
    display: none;
  }
  .vs-faq-accordion__row summary:focus-visible {
    outline: none;
    box-shadow: var(--vs-focus-ring);
  }
  .vs-faq-accordion__q {
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.35;
  }
  .vs-faq-accordion__a {
    margin: 0;
    padding: 0 22px 20px;
    font-size: 0.9375rem;
    line-height: 1.6;
    color: var(--vs-ink-2);
  }
  .vs-faq-accordion__mark {
    position: relative;
    flex: none;
    width: 28px;
    height: 28px;
    border-radius: var(--vs-radius-pill);
    background: var(--vs-field);
  }
  .vs-faq-accordion__mark::before,
  .vs-faq-accordion__mark::after {
    content: "";
    position: absolute;
    left: 8px;
    top: 13px;
    width: 12px;
    height: 2px;
    background: var(--vs-ink);
    transition: transform var(--vs-duration) var(--vs-ease-out);
  }
  .vs-faq-accordion__mark::after {
    transform: rotate(90deg);
  }
  .vs-faq-accordion__row[open] .vs-faq-accordion__mark::after {
    transform: rotate(0deg);
  }
</style>
```
