FAQ accordion
Questions and plain-text answers in a compact accordion. Visitors open the answers they need with disclosure buttons.
cmp_faq_accordion_001 Preview
Fit to the available width. Previews taller than the maximum auto-height scroll inside the frame.
Use this component with your coding agent
Using the PageSugar MCP server, fetch component cmp_faq_accordion_001 version 1.0.0 with variant "neutral", first inspect its requirements and license status and confirm this project uses Svelte 5 and Tailwind CSS 4. Retrieve every manifest file, including binary assets and any manifest-only response files, preserving relative paths. Then integrate the source and follow its usage notes. Run project checks, review the browser result and report anything unverified. Do not substitute another version or invent missing files.
Code
Neutral palette, version 1.0.0. Artifact sha256-23239c116daa334fa144c0d3418b21b6a5df93bc18f332c156ce8b0e0dddcfe0
FaqAccordion.svelte · Svelte component · 2.7 KB
<script module lang="ts">
export interface FaqItem {
question: string;
/** Plain text answer. Line breaks are preserved. */
answer: string;
}
</script>
<script lang="ts">
interface Props {
items: FaqItem[];
title: string;
description?: string;
headingLevel?: 2 | 3 | 4 | 5;
multiple?: boolean;
defaultOpen?: number[];
}
let {
items,
title,
description,
headingLevel = 2,
multiple = false,
defaultOpen = []
}: Props = $props();
const uid = $props.id();
const sectionHeading = $derived(`h${headingLevel}`);
const itemHeading = $derived(`h${headingLevel + 1}`);
function initialOpen(): number[] {
return multiple ? [...defaultOpen] : defaultOpen.slice(0, 1);
}
let open = $state<number[]>(initialOpen());
function toggle(index: number) {
if (open.includes(index)) {
open = open.filter((i) => i !== index);
} else {
open = multiple ? [...open, index] : [index];
}
}
</script>
<section class="px-4 py-16 sm:px-6 lg:px-8" aria-labelledby="{uid}-title">
<div class="mx-auto max-w-3xl">
<svelte:element
this={sectionHeading}
id="{uid}-title"
class="text-3xl font-semibold tracking-tight text-balance text-zinc-900 sm:text-4xl"
>
{title}
</svelte:element>
{#if description}
<p class="mt-4 text-base text-pretty text-zinc-600 sm:text-lg">{description}</p>
{/if}
<div class="mt-10 divide-y divide-zinc-200 border-y border-zinc-200">
{#each items as item, index (index)}
{@const expanded = open.includes(index)}
<div>
<svelte:element this={itemHeading} class="m-0 text-base font-medium">
<button
type="button"
id="{uid}-trigger-{index}"
aria-expanded={expanded}
aria-controls="{uid}-panel-{index}"
onclick={() => toggle(index)}
class="flex w-full items-start justify-between gap-6 py-5 text-left text-zinc-900 hover:text-zinc-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-900"
>
<span class="min-w-0 break-words">{item.question}</span>
<svg
class={[
'mt-0.5 size-5 shrink-0 text-zinc-500 transition-transform motion-reduce:transition-none',
expanded && 'rotate-180'
]}
viewBox="0 0 20 20"
fill="none"
aria-hidden="true"
>
<path
d="M5 7.5l5 5 5-5"
stroke="currentColor"
stroke-width="1.75"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
</svelte:element>
<div id="{uid}-panel-{index}" hidden={!expanded} class="pr-11 pb-5">
<p class="text-base break-words whitespace-pre-line text-zinc-600">{item.answer}</p>
</div>
</div>
{/each}
</div>
</div>
</section>
Usage
Supply questions and plain-text answers. Open/closed state is local to each instance and is not persisted, synced to the URL or reported through callbacks. No additional runtime packages beyond Svelte and Tailwind CSS and no backend.
Suggested location: src/lib/components/faq-accordion-01
Required props: items, title
Example
<!-- Illustrative content: replace example claims, prices and links before publishing. -->
<script lang="ts">
import FaqAccordion, { type FaqItem } from '$lib/components/faq-accordion-01/FaqAccordion.svelte';
const items: FaqItem[] = [
{ question: 'Can I change plans later?', answer: 'Yes. Plan changes apply from your next billing date.' },
{ question: 'Is there a free trial?', answer: 'Every plan includes a 14-day trial.' }
];
</script>
<FaqAccordion title="Frequently asked questions" {items} defaultOpen={[0]} />Limitations
- Answers are plain text; edit the source to render rich content or snippets.
- Collapsed answers are hidden until JavaScript runs; content in closed panels is not reachable without client-side hydration.
- defaultOpen is read once at creation; changing multiple later does not collapse answers already open, and open state is tracked by item position. Remount (e.g. with {#key}) after changing multiple or reordering items.
Using the accordion #
Copy FaqAccordion.svelte into src/lib/components/faq-accordion-01/. The quick-start example and props reference describe its inputs. Replace the illustrative questions and answers with your actual product information.
Managing open answers #
Clicking a question, or pressing Enter or Space on its button, toggles the answer. With multiple off, opening another answer closes the previous one.
defaultOpen is read once when the component is created. Changing multiple later does not collapse answers already open. State is tracked by index: remount the component with a Svelte {#key} block after reordering items or changing the interaction mode.
Customization
Change content through props and edit Tailwind classes in the source for colours, spacing and dividers. No colour tokens are declared.
- Content: pass items as { question, answer }; line breaks in answers are preserved.
- Headings: set headingLevel so the section fits the page outline; questions use the next level down.
- Behaviour: set multiple to allow several open answers; use defaultOpen to expand answers on load.
- Colours: text and divider colours are zinc utilities on the section, buttons and panels; replace them together and keep body text at 4.5:1 contrast.
- Width: the content column uses max-w-3xl; change it on the inner wrapper.
- Rich answers: change the answer type and replace the paragraph in the panel with your own markup or a snippet prop.
Props and content inputs
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
items | FaqItem[] | Yes | None | Questions and plain-text answers ({ question: string; answer: string }) in display order. |
title | string | Yes | None | Section heading text. |
description | string | No | None | Optional introductory paragraph under the heading. |
headingLevel | 2 | 3 | 4 | 5 | No | 2 | Level of the section heading; each question is wrapped in a heading one level lower. |
multiple | boolean | No | false | When false, opening one answer closes the others. When true, answers open independently. |
defaultOpen | number[] | No | [] | Zero-based indexes of answers expanded on first render. Only the first index is used when multiple is false. |
Dependencies and services
No additional runtime packages beyond Svelte and Tailwind CSS.
Integration boundaries
- Integration level: local-interaction.
- Requires client-side JavaScript to be interactive.
- Server-side rendering: supported.
Accessibility
- Follows the WAI-ARIA disclosure/accordion pattern: each question is a native button inside a heading, with aria-expanded and aria-controls pointing at its answer panel.
- Buttons are operable with Enter and Space and appear in normal Tab order; collapsed panels use the hidden attribute so their content is removed from the accessibility tree.
- IDs are derived from $props.id(), so several accordions on one page never collide and server and client markup match.
- The chevron is decorative and hidden from assistive technology; state is conveyed by aria-expanded.
- Focus is shown with a visible outline; the chevron animation is disabled when reduced motion is requested.
Known limitations
- Arrow-key navigation between questions is not implemented (it is optional in the APG accordion pattern).
License
Declared source license: MIT.
Default license approval is pending. See the license status before adopting the source.
Version history
Only the current release is available. Keep downloaded source and its receipt if you need to use it again later.
- 1.0.0 Published current · selected · 16 September 2026