FAQAccordion
Reusable faqaccordion component.
Usage
Components are auto-discovered. Use the component directly in any .wrn view:
<FAQAccordion
eyebrow="eyebrow"
title="Frequently asked questions"
description="description"
items="[]"
centered="true"
/>Legacy mount name: data-component="FAQAccordion".
Props and attributes
| Prop | Type | Requirement | Default |
|---|---|---|---|
class | string | Optional | "" |
eyebrow | string | Optional | "" |
title | string | Optional | "Frequently asked questions" |
description | string | Optional | "" |
items | string | Optional | [] |
centered | boolean | Optional | true |
compact | boolean | Optional | false |
allowMultiple | boolean | Optional | false |
defaultOpenIndex | number | Optional | 0 |
showContact | boolean | Optional | false |
contactText | string | Optional | "Still have questions?" |
contactLabel | string | Optional | "Contact support" |
contactHref | string | Optional | "/support" |
Slots
This component does not declare a slot.
Events
click
Source contract
Installed source: components/FAQAccordion.wrn
component FAQAccordion {
props {
class = ""
eyebrow = ""
title = "Frequently asked questions"
description = ""
items = []
centered = true
compact = false
allowMultiple = false
defaultOpenIndex = 0
showContact = false
contactText = "Still have questions?"
contactLabel = "Contact support"
contactHref = "/support"
}
state openIndexes = defaultOpenIndex >= 0 ? [defaultOpenIndex] : []
functions {
function isOpen(index) {
return openIndexes.includes(index)
}
function toggleItem(index) {
if (isOpen(index)) {
openIndexes = openIndexes.filter(
(itemIndex) => itemIndex !== index
)
} else if (allowMultiple) {
openIndexes = [...openIndexes, index]
} else {
openIndexes = [index]
}
}
}
view {
<section
aria-labelledby="faq-accordion-title"
class="w-full px-5 sm:px-8 lg:px-10 xl:px-12 {class}"
class:py-10="compact"
class:sm:py-12="compact"
class:py-14="!compact"
class:sm:py-16="!compact"
class:lg:py-20="!compact"
class:hidden="items.length === 0"
>
<div
class="mx-auto w-full"
class:max-w-5xl="centered"
class:max-w-7xl="!centered"
>
<div
class="gap-10"
class:flex="centered"
class:flex-col="centered"
class:grid="!centered"
class:lg:grid-cols-[minmax(260px,0.7fr)_minmax(0,1.3fr)]="!centered"
class:lg:items-start="!centered"
class:lg:gap-14="!centered"
>
<!-- Heading -->
<div
class="min-w-0"
class:mx-auto="centered"
class:max-w-3xl="centered"
class:text-center="centered"
class:max-w-xl="!centered"
class:lg:sticky="!centered"
class:lg:top-24="!centered"
>
<!-- Eyebrow -->
<p
class="text-xs font-bold uppercase tracking-[0.18em] text-indigo-600 dark:text-indigo-400"
class:hidden="eyebrow === ''"
>
{eyebrow}
</p>
<!-- Title -->
<h2
id="faq-accordion-title"
class="font-bold leading-tight tracking-tight text-slate-950 dark:text-white"
class:mt-3="eyebrow !== ''"
class:text-2xl="compact"
class:sm:text-3xl="compact"
class:text-3xl="!compact"
class:sm:text-4xl="!compact"
>
{title}
</h2>
<!-- Description -->
<p
class="mt-4 text-sm leading-7 text-slate-600 dark:text-slate-400"
class:hidden="description === ''"
class:sm:text-base="!compact"
class:mx-auto="centered"
class:max-w-2xl="centered"
class:max-w-xl="!centered"
>
{description}
</p>
<!-- Contact card -->
<div
class="mt-7 rounded-2xl border border-indigo-200/80 bg-linear-to-br from-indigo-50 via-white to-violet-50 p-5 shadow-sm dark:border-indigo-500/20 dark:from-indigo-500/10 dark:via-white/[0.03] dark:to-violet-500/10"
class:hidden="!showContact"
>
<div
class="flex items-center gap-4"
class:justify-center="centered"
>
<span
class="grid h-11 w-11 shrink-0 place-items-center rounded-xl bg-white text-indigo-600 shadow-sm ring-1 ring-inset ring-indigo-200 dark:bg-white/5 dark:text-indigo-300 dark:ring-indigo-500/20"
>
<span class="icon-[lucide--messages-square] h-5 w-5" aria-hidden="true" >
</span>
</span>
<div
class="min-w-0"
class:text-left="centered"
>
<p class="text-sm font-semibold text-slate-950 dark:text-white">
{contactText}
</p>
{#if contactLabel !== '' && contactHref !== ''}
<a
href="{contactHref}"
class="mt-1 inline-flex items-center gap-1.5 text-sm font-bold text-indigo-600 transition-colors duration-200 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
>
{contactLabel}
<span class="icon-[lucide--arrow-right] h-4 w-4" aria-hidden="true" >
</span>
</a>
{/if}
</div>
</div>
</div>
</div>
<!-- Accordion -->
<div class="min-w-0">
<div class="space-y-3">
{#each items as item, index}
<article
class=" overflow-hidden rounded-2xl border transition-[border-color,background-color,box-shadow,transform] duration-300 ease-out {isOpen(index) ? 'border-indigo-200 bg-indigo-50/60 shadow-md shadow-indigo-950/5 dark:border-indigo-500/20 dark:bg-indigo-500/10' : 'border-slate-200 bg-white shadow-sm hover:-translate-y-0.5 hover:border-indigo-200 hover:bg-slate-50 dark:border-white/10 dark:bg-white/[0.03] dark:hover:border-indigo-500/20 dark:hover:bg-white/5' } "
>
<h3>
<button
type="button"
@click="toggleItem(index)"
aria-expanded="{isOpen(index)}"
aria-controls="faq-answer-{index}"
class="flex w-full items-center justify-between gap-5 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500"
class:px-4="compact"
class:py-4="compact"
class:px-5="!compact"
class:py-5="!compact"
class:sm:px-6="!compact"
>
<span class="flex min-w-0 items-start gap-3">
<!-- Number -->
<span
class=" mt-0.5 grid h-7 w-7 shrink-0 place-items-center rounded-lg text-xs font-bold transition-[background-color,color,transform] duration-300 ease-out {isOpen(index) ? 'bg-indigo-600 text-white' : 'bg-slate-100 text-slate-500 dark:bg-white/5 dark:text-slate-400' } "
>
{index + 1}
</span>
<!-- Question -->
<span
class="font-semibold leading-6 text-slate-950 dark:text-white"
class:text-sm="compact"
class:text-base="!compact"
>
{item.question}
</span>
</span>
<!-- Toggle icon -->
<span
class=" grid h-8 w-8 shrink-0 place-items-center rounded-lg transition-[background-color,color,transform] duration-300 ease-out {isOpen(index) ? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300' : 'bg-slate-100 text-slate-500 dark:bg-white/5 dark:text-slate-400' } "
>
<span class=" icon-[lucide--chevron-down] h-4 w-4 transition-transform duration-300 ease-out {isOpen(index) ? 'rotate-180' : 'rotate-0'} " aria-hidden="true" >
</span>
</span>
</button>
</h3>
<div
id="faq-answer-{index}"
role="region"
aria-hidden="{!isOpen(index)}"
class=" grid overflow-hidden transition-[grid-template-rows,opacity] duration-300 ease-out {isOpen(index) ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0' } "
>
<div class="min-h-0 overflow-hidden">
<div class="border-t border-indigo-200/60 dark:border-indigo-500/15">
<div
class=" text-sm leading-7 text-slate-600 transition-[transform,opacity] duration-300 ease-out dark:text-slate-400 {compact ? 'px-4 pb-4 pt-3' : 'px-5 pb-5 pt-4 sm:px-6 sm:pb-6 sm:pt-5' } {isOpen(index) ? 'translate-y-0 opacity-100' : '-translate-y-2 opacity-0' } "
>
<p class="m-0">
{item.answer}
</p>
{#if item.linkLabel !== '' && item.href !== ''}
<a
href="{item.href}"
class=" group inline-flex items-center gap-2 text-sm font-bold text-indigo-600 transition-colors duration-200 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300 "
>
{item.linkLabel}
<span class=" icon-[lucide--arrow-up-right] h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 " aria-hidden="true" >
</span>
</a>
{/if}
</div>
</div>
</div>
</div>
</article>
{/each}
</div>
</div>
</div>
</div>
<!-- Tailwind safelist for utilities used by reactive class directives -->
<span class=" hidden flex flex-col grid justify-center lg:gap-14 lg:grid-cols-[minmax(260px,0.7fr)_minmax(0,1.3fr)] lg:items-start lg:py-20 lg:sticky lg:top-24 max-w-2xl max-w-3xl max-w-5xl max-w-7xl max-w-xl mt-3 mx-auto px-4 px-5 py-4 py-5 py-10 py-14 sm:px-6 sm:py-12 sm:py-16 sm:text-base sm:text-3xl sm:text-4xl text-base text-center text-left text-sm text-2xl text-3xl grid-rows-[0fr] grid-rows-[1fr] opacity-0 opacity-100 -translate-y-2 translate-y-0 rotate-0 rotate-180 " >
</span>
</section>
}
}