---
/**
* 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>