1 звезда на GitHub = 1 новый компонент.

Поставить звезду
Вопросы и ответы (FAQ)

FAQ Two Column

FAQ в две колонки с закреплённым вступлением и нативными аккордеонами <details> без JS.

faqaccordiondetailssupport
Живое превью

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.

Использование

Скопируйте и вставьте этот код на вашу страницу Astro:

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

Ручная установка

Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/sections/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>