Zoeken...  ⌘K GitHub

FAQCategorized UI Elements

FAQ met tabbed categorieën en icon headers. Toegankelijk, keyboard-navigeerbaar.

/faq-categorized
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