FAQCategorized UI Elements
FAQ met tabbed categorieën en icon headers. Toegankelijk, keyboard-navigeerbaar.
src/components/ui/FAQCategorized.astro
---
/**
* FAQCategorized
* FAQ met categorieën — tabbed of gesectioneerd per groep met icon headers.
*/
interface FAQItem {
question: string;
answer: string;
}
interface FAQCategory {
id: string;
label: string;
icon?: string;
items: FAQItem[];
}
interface Props {
eyebrow?: string;
headline?: string;
sub?: string;
categories: FAQCategory[];
}
const { eyebrow, headline, sub, categories = [] } = Astro.props;
---
<section class="faqc" data-component="faq-categorized">
<div class="faqc__inner">
<div class="faqc__header">
{eyebrow && <p class="faqc__eyebrow">{eyebrow}</p>}
{headline && <h2 class="faqc__title" set:html={headline} />}
{sub && <p class="faqc__sub">{sub}</p>}
</div>
<!-- Category tabs -->
<div class="faqc__tabs" role="tablist">
{categories.map((cat, i) => (
<button
class:list={['faqc__tab', { 'is-active': i === 0 }]}
role="tab"
data-cat={cat.id}
aria-selected={i === 0 ? 'true' : 'false'}
>
{cat.icon && <span class="faqc__tab-icon" set:html={cat.icon} />}
{cat.label}
</button>
))}
</div>
<!-- FAQ panels -->
{categories.map((cat, i) => (
<div
class:list={['faqc__panel', { 'is-active': i === 0 }]}
data-panel={cat.id}
role="tabpanel"
>
{cat.items.map((item, j) => (
<details class="faqc__item" open={j === 0 && i === 0}>
<summary class="faqc__question">
{item.question}
<svg class="faqc__chevron" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</summary>
<div class="faqc__answer">
<p>{item.answer}</p>
</div>
</details>
))}
</div>
))}
</div>
</section>
<style>
.faqc {
background: var(--color-bg, #fff);
padding: 5rem 1.5rem;
}
.faqc__inner {
max-width: 800px;
margin: 0 auto;
}
.faqc__header {
text-align: center;
margin-bottom: 3rem;
}
.faqc__eyebrow {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-accent, #6366f1);
margin-bottom: 0.75rem;
}
.faqc__title {
font-size: clamp(1.875rem, 3vw, 2.75rem);
font-weight: 800;
letter-spacing: -0.03em;
color: var(--color-primary, #0a0a0a);
margin-bottom: 0.875rem;
}
.faqc__title :global(em) {
font-style: normal;
color: var(--color-accent, #6366f1);
}
.faqc__sub {
font-size: 1.0625rem;
color: var(--color-muted, #6b7280);
line-height: 1.6;
}
/* Tabs */
.faqc__tabs {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 2rem;
border-bottom: 2px solid rgba(0,0,0,0.07);
padding-bottom: 0;
}
.faqc__tab {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1.25rem;
border: none;
background: none;
font-size: 0.9rem;
font-weight: 600;
color: var(--color-muted, #6b7280);
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
transition: color 0.15s, border-color 0.15s;
font-family: inherit;
}
.faqc__tab.is-active {
color: var(--color-accent, #6366f1);
border-bottom-color: var(--color-accent, #6366f1);
}
.faqc__tab:hover { color: var(--color-primary, #0a0a0a); }
.faqc__tab-icon :global(svg) {
width: 16px;
height: 16px;
}
/* Panels */
.faqc__panel { display: none; }
.faqc__panel.is-active { display: block; }
/* FAQ items */
.faqc__item {
border-bottom: 1px solid rgba(0,0,0,0.07);
}
.faqc__item:first-child { border-top: 1px solid rgba(0,0,0,0.07); }
.faqc__question {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.25rem 0;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
color: var(--color-primary, #0a0a0a);
list-style: none;
gap: 1rem;
}
.faqc__question::-webkit-details-marker { display: none; }
.faqc__chevron {
flex-shrink: 0;
transition: transform 0.25s;
color: var(--color-muted, #6b7280);
}
details[open] .faqc__chevron {
transform: rotate(180deg);
}
details[open] .faqc__question {
color: var(--color-accent, #6366f1);
}
.faqc__answer {
padding-bottom: 1.25rem;
}
.faqc__answer p {
font-size: 0.9375rem;
line-height: 1.7;
color: var(--color-muted, #6b7280);
}
</style>
<script>
const tabs = document.querySelectorAll('.faqc__tab');
const panels = document.querySelectorAll('.faqc__panel');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const cat = (tab as HTMLElement).dataset.cat;
tabs.forEach(t => {
t.classList.remove('is-active');
t.setAttribute('aria-selected', 'false');
});
panels.forEach(p => p.classList.remove('is-active'));
tab.classList.add('is-active');
tab.setAttribute('aria-selected', 'true');
document.querySelector(`[data-panel="${cat}"]`)?.classList.add('is-active');
});
});
</script>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
categories * | FAQCategory[] | — | Array van categorieën met items |
eyebrow | string | — | Label boven titel |
headline | string | — | Sectie headline |
sub | string | — | Ondertitel |
* = verplicht