1 GitHub star = 1 new component.

Star the repo
Page Sections faqaccordiondetailssupport

FAQ Two Column

Two-column FAQ with sticky intro and native exclusive <details> accordions that work without JS.

Live Preview

Frequently asked questions

Everything you need to know about the kit. Can’t find an answer? Our team replies within a day.

Contact support
Do I need to install a package?

No. Every component is a standalone .astro file. Copy it into your project, or install the package if you prefer imports.

Do the charts need a JavaScript library?

No. The SVG charts render at build time. A tiny shared script only powers hover tooltips, and every chart includes a data table fallback.

Is it accessible?

Components target WCAG 2.2 AA: semantic HTML, visible focus states, keyboard support and contrast of at least 4.5:1 for text.

Can I use it in commercial projects?

Yes. The kit is MIT licensed. Use it for client work, SaaS products and anything else.

How do I change the colors?

Edit the CSS custom properties in global.css. Every component reads from the same tokens, so one change updates everything.

Usage

Copy and paste this code into your Astro page:

astro
---
import { FaqTwoColumn } from 'astro-component-kit/sections';
---

<FaqTwoColumn
  items={[
    { q: 'Is it free?', a: 'Yes, MIT licensed.' },
    { q: 'Do I need JS?', a: 'No.' },
  ]}
  exclusive
/>
--- import { FaqTwoColumn } from 'astro-component-kit/sections'; --- <FaqTwoColumn items={[ { q: 'Is it free?', a: 'Yes, MIT licensed.' }, { q: 'Do I need JS?', a: 'No.' }, ]} exclusive />

Manual Installation

If you are not using the npm package, create a new file and paste the following code: src/components/lib/FAQTwoColumn.astro:

astro
---
/**
 * FaqTwoColumn — FAQ section with a sticky intro column (heading + support CTA)
 * and native <details> accordions on the right. Works without JavaScript.
 *
 * @param {string} title - Section heading.
 * @param {string} subtitle - Intro copy.
 * @param {Array<{q: string, a: string}>} items - Questions and answers.
 * @param {{label: string, href: string}} support - Support link.
 * @param {boolean} exclusive - Only one item open at a time (uses the `name` attribute).
 * @param {string} variant - Item style (bordered|cards|plain).
 * @param {string} size - Vertical rhythm (sm|md|lg).
 * @param {string} className - Extra CSS class.
 */
interface Props {
  title?: string;
  subtitle?: string;
  items?: Array<{ q: string; a: string }>;
  support?: { label: string; href: string };
  exclusive?: boolean;
  variant?: 'bordered' | 'cards' | 'plain';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

const {
  title = 'Frequently asked questions',
  subtitle = 'Everything you need to know about the kit. Can’t find an answer? Our team replies within a day.',
  items = [
    { q: 'Do I need to install a package?', a: 'No. Every component is a standalone .astro file. Copy it into your project, or install the package if you prefer imports.' },
    { q: 'Do the charts need a JavaScript library?', a: 'No. The SVG charts render at build time. A tiny shared script only powers hover tooltips, and every chart includes a data table fallback.' },
    { q: 'Is it accessible?', a: 'Components target WCAG 2.2 AA: semantic HTML, visible focus states, keyboard support and contrast of at least 4.5:1 for text.' },
    { q: 'Can I use it in commercial projects?', a: 'Yes. The kit is MIT licensed. Use it for client work, SaaS products and anything else.' },
    { q: 'How do I change the colors?', a: 'Edit the CSS custom properties in global.css. Every component reads from the same tokens, so one change updates everything.' },
  ],
  support = { label: 'Contact support', href: '#' },
  exclusive = true,
  variant = 'bordered',
  size = 'md',
  className = '',
} = Astro.props;

const uid = Math.random().toString(36).slice(2, 8);
const id = `ftc-${uid}`;
---

<div class="cq-faq-two-column-cq">
<section class={`ftc ftc--${variant} ftc--${size} ${className}`} aria-labelledby={id}>
  <div class="ftc__inner">
    <div class="ftc__intro">
      <h2 id={id} class="ftc__title">{title}</h2>
      <p class="ftc__subtitle">{subtitle}</p>
      {support && (
        <a class="ftc__support" href={support.href}>
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg>
          {support.label}
        </a>
      )}
    </div>
    <div class="ftc__list">
      {items.map((it, i) => (
        <details class="ftc__item" name={exclusive ? `faq-${uid}` : undefined} open={i === 0}>
          <summary class="ftc__q">
            <span>{it.q}</span>
            <svg class="ftc__chev" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
          </summary>
          <div class="ftc__a"><p>{it.a}</p></div>
        </details>
      ))}
    </div>
  </div>
</section>
</div>
<style>
  .cq-faq-two-column-cq { container-type: inline-size; }
  .ftc {
    --ftc-py: 5.5rem;
    padding: var(--ftc-py) var(--sp-6, 1.5rem);
    background: var(--c-bg, #050505);
  }
  .ftc--sm { --ftc-py: 3.5rem; }
  .ftc--lg { --ftc-py: 7.5rem; }
  .ftc__inner { max-width: 1120px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1.6fr; gap: clamp(2rem, 6vw, 5rem); align-items: start; }
  .ftc__intro { position: sticky; top: 6rem; }
  .ftc__title { margin: 0 0 1rem; font-size: clamp(1.9rem, 4vw, 2.5rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.12; color: var(--c-text-1, #fff); }
  .ftc__subtitle { margin: 0 0 1.75rem; font-size: 1rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
  .ftc__support { display: inline-flex; align-items: center; gap: 0.5rem; min-height: 46px; padding: 0 1.25rem; border-radius: var(--r-md, 12px); border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.14); color: var(--c-text-1, #fff); font-weight: 600; text-decoration: none; transition: border-color 0.2s ease; }
  .ftc__support:hover { border-color: rgb(var(--ink-rgb, 255 255 255) / 0.32); color: var(--c-text-1, #fff); }
  .ftc__support:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
  .ftc__list { display: grid; }
  .ftc--cards .ftc__list { gap: 0.75rem; }
  .ftc--bordered .ftc__item { border-bottom: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08); }
  .ftc--bordered .ftc__item:first-child { border-top: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08); }
  .ftc--cards .ftc__item { border-radius: var(--r-md, 14px); background: rgb(var(--ink-rgb, 255 255 255) / 0.025); border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.07)); padding-inline: 1.25rem; }
  .ftc__q {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    min-height: 64px;
    padding: 1rem 0;
    cursor: pointer;
    list-style: none;
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--c-text-1, #fff);
  }
  .ftc__q::-webkit-details-marker { display: none; }
  .ftc__q:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; border-radius: 6px; }
  .ftc__chev { flex-shrink: 0; color: var(--c-text-2, #a3a3a3); transition: transform 0.25s ease; }
  .ftc__item[open] .ftc__chev { transform: rotate(45deg); color: var(--c-primary-light, #d4a017); }
  .ftc__a { padding: 0 2.5rem 1.25rem 0; }
  .ftc__a p { margin: 0; font-size: 0.9875rem; line-height: 1.7; color: var(--c-text-2, #a3a3a3); }
  .ftc__item[open] .ftc__a { animation: ftc-in 0.25s ease; }
  @keyframes ftc-in { from { opacity: 0; transform: translateY(-4px); } }
  @container (max-width: 820px) {
    .ftc__inner { grid-template-columns: 1fr; }
    .ftc__intro { position: static; }
  }
  @media (prefers-reduced-motion: reduce) { .ftc__chev { transition: none; } .ftc__item[open] .ftc__a { animation: none; } }
</style>
--- /** * FaqTwoColumn — FAQ section with a sticky intro column (heading + support CTA) * and native <details> accordions on the right. Works without JavaScript. * * @param {string} title - Section heading. * @param {string} subtitle - Intro copy. * @param {Array<{q: string, a: string}>} items - Questions and answers. * @param {{label: string, href: string}} support - Support link. * @param {boolean} exclusive - Only one item open at a time (uses the `name` attribute). * @param {string} variant - Item style (bordered|cards|plain). * @param {string} size - Vertical rhythm (sm|md|lg). * @param {string} className - Extra CSS class. */ interface Props { title?: string; subtitle?: string; items?: Array<{ q: string; a: string }>; support?: { label: string; href: string }; exclusive?: boolean; variant?: 'bordered' | 'cards' | 'plain'; size?: 'sm' | 'md' | 'lg'; className?: string; } const { title = 'Frequently asked questions', subtitle = 'Everything you need to know about the kit. Can’t find an answer? Our team replies within a day.', items = [ { q: 'Do I need to install a package?', a: 'No. Every component is a standalone .astro file. Copy it into your project, or install the package if you prefer imports.' }, { q: 'Do the charts need a JavaScript library?', a: 'No. The SVG charts render at build time. A tiny shared script only powers hover tooltips, and every chart includes a data table fallback.' }, { q: 'Is it accessible?', a: 'Components target WCAG 2.2 AA: semantic HTML, visible focus states, keyboard support and contrast of at least 4.5:1 for text.' }, { q: 'Can I use it in commercial projects?', a: 'Yes. The kit is MIT licensed. Use it for client work, SaaS products and anything else.' }, { q: 'How do I change the colors?', a: 'Edit the CSS custom properties in global.css. Every component reads from the same tokens, so one change updates everything.' }, ], support = { label: 'Contact support', href: '#' }, exclusive = true, variant = 'bordered', size = 'md', className = '', } = Astro.props; const uid = Math.random().toString(36).slice(2, 8); const id = `ftc-${uid}`; --- <div class="cq-faq-two-column-cq"> <section class={`ftc ftc--${variant} ftc--${size} ${className}`} aria-labelledby={id}> <div class="ftc__inner"> <div class="ftc__intro"> <h2 id={id} class="ftc__title">{title}</h2> <p class="ftc__subtitle">{subtitle}</p> {support && ( <a class="ftc__support" href={support.href}> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg> {support.label} </a> )} </div> <div class="ftc__list"> {items.map((it, i) => ( <details class="ftc__item" name={exclusive ? `faq-${uid}` : undefined} open={i === 0}> <summary class="ftc__q"> <span>{it.q}</span> <svg class="ftc__chev" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg> </summary> <div class="ftc__a"><p>{it.a}</p></div> </details> ))} </div> </div> </section> </div> <style> .cq-faq-two-column-cq { container-type: inline-size; } .ftc { --ftc-py: 5.5rem; padding: var(--ftc-py) var(--sp-6, 1.5rem); background: var(--c-bg, #050505); } .ftc--sm { --ftc-py: 3.5rem; } .ftc--lg { --ftc-py: 7.5rem; } .ftc__inner { max-width: 1120px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1.6fr; gap: clamp(2rem, 6vw, 5rem); align-items: start; } .ftc__intro { position: sticky; top: 6rem; } .ftc__title { margin: 0 0 1rem; font-size: clamp(1.9rem, 4vw, 2.5rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.12; color: var(--c-text-1, #fff); } .ftc__subtitle { margin: 0 0 1.75rem; font-size: 1rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); } .ftc__support { display: inline-flex; align-items: center; gap: 0.5rem; min-height: 46px; padding: 0 1.25rem; border-radius: var(--r-md, 12px); border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.14); color: var(--c-text-1, #fff); font-weight: 600; text-decoration: none; transition: border-color 0.2s ease; } .ftc__support:hover { border-color: rgb(var(--ink-rgb, 255 255 255) / 0.32); color: var(--c-text-1, #fff); } .ftc__support:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; } .ftc__list { display: grid; } .ftc--cards .ftc__list { gap: 0.75rem; } .ftc--bordered .ftc__item { border-bottom: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08); } .ftc--bordered .ftc__item:first-child { border-top: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08); } .ftc--cards .ftc__item { border-radius: var(--r-md, 14px); background: rgb(var(--ink-rgb, 255 255 255) / 0.025); border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.07)); padding-inline: 1.25rem; } .ftc__q { display: flex; justify-content: space-between; align-items: center; gap: 1rem; min-height: 64px; padding: 1rem 0; cursor: pointer; list-style: none; font-size: 1.0625rem; font-weight: 600; color: var(--c-text-1, #fff); } .ftc__q::-webkit-details-marker { display: none; } .ftc__q:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; border-radius: 6px; } .ftc__chev { flex-shrink: 0; color: var(--c-text-2, #a3a3a3); transition: transform 0.25s ease; } .ftc__item[open] .ftc__chev { transform: rotate(45deg); color: var(--c-primary-light, #d4a017); } .ftc__a { padding: 0 2.5rem 1.25rem 0; } .ftc__a p { margin: 0; font-size: 0.9875rem; line-height: 1.7; color: var(--c-text-2, #a3a3a3); } .ftc__item[open] .ftc__a { animation: ftc-in 0.25s ease; } @keyframes ftc-in { from { opacity: 0; transform: translateY(-4px); } } @container (max-width: 820px) { .ftc__inner { grid-template-columns: 1fr; } .ftc__intro { position: static; } } @media (prefers-reduced-motion: reduce) { .ftc__chev { transition: none; } .ftc__item[open] .ftc__a { animation: none; } } </style>

Quick Info

Category
Page Sections
Filename
FAQTwoColumn.astro
Dependencies
None — pure Astro + CSS
Tags
faqaccordiondetailssupport