Skip to content

Two-column FAQ

All answers visible, so the page stays searchable with the browser's find.

Marketingstarterfaqopensearchabletwo-column

Live preview

full widthLive preview — open it in a new tab for the full-height version.
Open the preview in a new tab

Source

This exact file renders the preview above.

import Link from 'next/link'
import { ArrowRight } from 'lucide-react'
import { Container } from '@/components/ui/layout'
import { faqs } from '@/content/demo'

/**
 * Two-column FAQ
 *
 * All answers visible, no disclosure. When the answers are short, hiding them
 * behind a click costs more than it saves — and makes the page unsearchable
 * with Ctrl+F, which is how people actually use an FAQ.
 */
export default function TwoColumnFAQ() {
  return (
    <section className="border-b border-line bg-surface-sunken py-section">
      <Container>
        <div className="grid gap-10 lg:grid-cols-[0.7fr_1.3fr] lg:gap-16">
          <div>
            <h2 className="display-type text-2xl font-semibold text-ink-strong sm:text-3xl">
              Frequently asked
            </h2>
            <p className="mt-3 text-md text-ink-muted">
              Answers are visible rather than hidden, so the page stays searchable.
            </p>
            <Link
              href="/forms/support-request"
              className="mt-5 inline-flex items-center gap-1.5 text-sm font-semibold text-accent underline underline-offset-4"
            >
              Ask something else
              <ArrowRight className="size-4" aria-hidden="true" />
            </Link>
          </div>

          <dl className="grid gap-x-10 gap-y-8 sm:grid-cols-2">
            {faqs.slice(0, 6).map((faq) => (
              <div key={faq.question}>
                <dt className="text-sm font-semibold text-ink-strong">{faq.question}</dt>
                <dd className="mt-2 text-sm leading-relaxed text-ink-muted">{faq.answer}</dd>
              </div>
            ))}
          </dl>
        </div>
      </Container>
    </section>
  )
}

components/blocks/sections/faq/two-column.tsx

import Link from 'next/link'
import { ArrowRight } from 'lucide-react'
import { Container } from '@/components/ui/layout'
import { faqs } from '@/content/demo'

/**
 * Two-column FAQ
 *
 * All answers visible, no disclosure. When the answers are short, hiding them
 * behind a click costs more than it saves — and makes the page unsearchable
 * with Ctrl+F, which is how people actually use an FAQ.
 */
export default function TwoColumnFAQ() {
  return (
    <section className="border-b border-line bg-surface-sunken py-section">
      <Container>
        <div className="grid gap-10 lg:grid-cols-[0.7fr_1.3fr] lg:gap-16">
          <div>
            <h2 className="display-type text-2xl font-semibold text-ink-strong sm:text-3xl">
              Frequently asked
            </h2>
            <p className="mt-3 text-md text-ink-muted">
              Answers are visible rather than hidden, so the page stays searchable.
            </p>
            <Link
              href="/forms/support-request"
              className="mt-5 inline-flex items-center gap-1.5 text-sm font-semibold text-accent underline underline-offset-4"
            >
              Ask something else
              <ArrowRight className="size-4" aria-hidden="true" />
            </Link>
          </div>

          <dl className="grid gap-x-10 gap-y-8 sm:grid-cols-2">
            {faqs.slice(0, 6).map((faq) => (
              <div key={faq.question}>
                <dt className="text-sm font-semibold text-ink-strong">{faq.question}</dt>
                <dd className="mt-2 text-sm leading-relaxed text-ink-muted">{faq.answer}</dd>
              </div>
            ))}
          </dl>
        </div>
      </Container>
    </section>
  )
}

content/demo.ts

/**
 * Shared demo content.
 *
 * Marketing copy lives here rather than inside JSX, so a section file stays a
 * layout decision and nothing else. Every section in the library draws from
 * this module, which is also what keeps 110 sections from becoming 110 copies
 * of the same paragraph.
 *
 * All names, companies and figures are fictional.
 */

export interface Feature {
  title: string
  description: string
  /** lucide-react icon name, resolved by the consuming section. */
  icon: string
}

export const features: Feature[] = [
  {
    title: 'Token-driven theming',
    description:
      'Three orthogonal axes — scheme, palette, density — compose into every surface. Change one and the whole system follows.',
    icon: 'Palette',
  },
  {
    title: 'Accessible by construction',
    description:
      'Focus management, live regions and keyboard contracts are built into the primitives, not bolted on during review.',
    icon: 'Accessibility',
  },
  {
    title: 'Server-first rendering',
    description:
      'Sections are Server Components. Interactivity is scoped to small client islands, so a marketing page ships almost no JavaScript.',
    icon: 'Server',
  },
  {
    title: 'Composable sections',
    description:
      'Every block is a standalone export. Assemble a page from nine of them, or lift one into an existing codebase.',
    icon: 'Layers',
  },
  {
    title: 'Real source, always',
    description:
      'The code viewer reads the same file that renders the preview. Documentation cannot drift from implementation.',
    icon: 'Code2',
  },
  {
    title: 'Built to grow',
    description:
      'Adding a component means adding a catalogue record and a file. No page in the application needs to change.',
    icon: 'Sprout',
  },
]

export const secondaryFeatures: Feature[] = [
  {
    title: 'Responsive previews',
    description: 'Inspect any block at 390, 768 and 1440 without leaving the page.',
    icon: 'Smartphone',
  },
  {
    title: 'Deterministic copy',
    description: 'The copy button copies exactly what the viewer displays — no rewriting.',
    icon: 'Clipboard',
  },
  {
    title: 'Typed catalogue',
    description: 'One record shape across six families, validated by the test suite.',
    icon: 'ListTree',
  },
  {
    title: 'Zero runtime CSS',
    description: 'Tailwind v4 plus custom properties. No styling library in the bundle.',
    icon: 'Feather',
  },
  {
    title: 'Keyboard everything',
    description: 'Menus, tabs, dialogs and the palette all follow published ARIA patterns.',
    icon: 'Keyboard',
  },
  {
    title: 'Dark authored, not inverted',
    description: 'The dark scheme is designed, not derived from a filter.',
    icon: 'Moon',
  },
]

export interface Stat {
  value: string
  label: string
  detail?: string
}

export const stats: Stat[] = [
  { value: '4', label: 'Composition levels', detail: 'Primitives through complete products' },
  { value: '3', label: 'Theme axes', detail: 'Scheme, palette, density' },
  { value: '0', label: 'Runtime dependencies', detail: 'Beyond React, Next and icons' },
  { value: '100%', label: 'Statically rendered', detail: 'Every catalogue route' },
]

export const companyLogos = [
  'Northwind',
  'Halcyon',
  'Meridian',
  'Kestrel',
  'Lumen Works',
  'Atlas Forge',
  'Verdant',
  'Ironwood',
]

export interface Testimonial {
  quote: string
  name: string
  role: string
  company: string
}

export const testimonials: Testimonial[] = [
  {
    quote:
      'We replaced four half-finished internal libraries with one. The density axis alone paid for the migration — our admin tools and our marketing site finally share components.',
    name: 'Priya Raman',
    role: 'Principal Engineer',
    company: 'Northwind',
  },
  {
    quote:
      'The accessibility work is the part I did not expect. Focus return, live regions, manual tab activation — the things that normally get filed as tech debt were already handled.',
    name: 'Tomas Lindqvist',
    role: 'Head of Design Systems',
    company: 'Halcyon',
  },
  {
    quote:
      'Being able to read the exact source that renders the preview removed an entire class of "the docs are wrong" tickets.',
    name: 'Amara Osei',
    role: 'Staff Frontend Engineer',
    company: 'Meridian',
  },
  {
    quote:
      'Our first landing page took an afternoon. The second took forty minutes, because by then we were composing rather than building.',
    name: 'Jun Watanabe',
    role: 'Product Lead',
    company: 'Kestrel',
  },
  {
    quote:
      'Five palettes sounds like a gimmick until a client asks for a different accent on the third day and it is a one-line change.',
    name: 'Elena Rossi',
    role: 'Creative Director',
    company: 'Lumen Works',
  },
  {
    quote:
      'The starters are the honest part. They are not screenshots — every route resolves, including the 404s.',
    name: 'Marcus Bell',
    role: 'Engineering Manager',
    company: 'Atlas Forge',
  },
]

export interface PricingTier {
  name: string
  price: string
  cadence: string
  description: string
  features: string[]
  cta: string
  featured?: boolean
  note?: string
}

export const pricingTiers: PricingTier[] = [
  {
    name: 'Solo',
    price: '$0',
    cadence: 'forever',
    description: 'For personal projects and evaluation.',
    features: [
      'Every component and section',
      'Copy-paste source',
      'Light and dark schemes',
      'Community support',
    ],
    cta: 'Start building',
  },
  {
    name: 'Team',
    price: '$18',
    cadence: 'per seat / month',
    description: 'For product teams shipping together.',
    features: [
      'Everything in Solo',
      'All five palettes',
      'Starter products',
      'Figma token export',
      'Priority issue triage',
    ],
    cta: 'Start a trial',
    featured: true,
    note: 'Most teams start here.',
  },
  {
    name: 'Business',
    price: '$2,400',
    cadence: 'per year',
    description: 'For organisations with a design-system function.',
    features: [
      'Everything in Team',
      'Unlimited seats',
      'Private component registry',
      'Accessibility review sessions',
      'Named support contact',
    ],
    cta: 'Contact sales',
  },
]

export interface FAQ {
  question: string
  answer: string
}

export const faqs: FAQ[] = [
  {
    question: 'Is Foundry a published npm package?',
    answer:
      'No. Foundry is a template demonstration. Components are copied into your project and owned by you — there is no registry, no install command and no version to upgrade.',
  },
  {
    question: 'How do I change the accent colour?',
    answer:
      'Every accent in the system resolves from one ramp of custom properties. Redefine those seven values in your stylesheet and every button, focus ring, link and badge follows.',
  },
  {
    question: 'Do the components require JavaScript?',
    answer:
      'Most do not. Sections, cards, tables, forms and layout primitives render entirely on the server. Only overlays, menus and the command palette are client islands.',
  },
  {
    question: 'What does the density axis actually change?',
    answer:
      'Control heights, control padding, stack rhythm, card padding, table row height and section padding. It deliberately does not change the type scale, so text stays legible at every density.',
  },
  {
    question: 'Can I use only part of the library?',
    answer:
      'Yes. Each block is a standalone file whose imports are visible in the code viewer. Copying one section brings the primitives it names and nothing else.',
  },
  {
    question: 'How is the source in the documentation kept accurate?',
    answer:
      'A build step reads the real files from disk and generates the source registry. The preview and the code you copy come from the same file, and a test fails if the two ever diverge.',
  },
  {
    question: 'Is there a Figma library?',
    answer:
      'The token manifest is designed to be exported to Figma variables, but no Figma file ships with this demo.',
  },
  {
    question: 'What browsers are supported?',
    answer:
      'Current versions of Chrome, Edge, Safari and Firefox. The library uses container-free responsive CSS and standard custom properties throughout.',
  },
]

export interface TeamMember {
  name: string
  role: string
  bio: string
  location: string
}

export const team: TeamMember[] = [
  {
    name: 'Priya Raman',
    role: 'Design systems',
    bio: 'Spent six years untangling component libraries before deciding to write one properly.',
    location: 'Bengaluru',
  },
  {
    name: 'Tomas Lindqvist',
    role: 'Accessibility',
    bio: 'Audits by day, writes focus-management utilities by night.',
    location: 'Stockholm',
  },
  {
    name: 'Amara Osei',
    role: 'Frontend architecture',
    bio: 'Believes most performance problems are really architecture problems.',
    location: 'Accra',
  },
  {
    name: 'Jun Watanabe',
    role: 'Product',
    bio: 'Turns "we should document this" into things that are actually documented.',
    location: 'Osaka',
  },
  {
    name: 'Elena Rossi',
    role: 'Visual design',
    bio: 'Editorial background; still measures leading by eye and is usually right.',
    location: 'Milan',
  },
  {
    name: 'Marcus Bell',
    role: 'Developer experience',
    bio: 'Optimises for the twentieth time you use something, not the first.',
    location: 'Manchester',
  },
  {
    name: 'Sofia Delgado',
    role: 'Documentation',
    bio: 'Thinks a component without a usage note is only half shipped.',
    location: 'Madrid',
  },
  {
    name: 'Idris Karim',
    role: 'Infrastructure',
    bio: 'Keeps the build under a minute so nobody is tempted to skip it.',
    location: 'Toronto',
  },
]

export interface ProcessStep {
  title: string
  description: string
  duration?: string
}

export const processSteps: ProcessStep[] = [
  {
    title: 'Audit',
    description:
      'Inventory every surface, control and one-off in the existing product. Nothing is designed before the mess is measured.',
    duration: 'Week 1–2',
  },
  {
    title: 'Tokenise',
    description:
      'Extract the real colour, type and spacing decisions into a token set the whole team can read.',
    duration: 'Week 3',
  },
  {
    title: 'Build primitives',
    description:
      'Ship the twenty components that account for eighty per cent of screens, with states and keyboard contracts.',
    duration: 'Week 4–7',
  },
  {
    title: 'Compose',
    description:
      'Assemble sections and page patterns, then migrate a real route to prove the system holds.',
    duration: 'Week 8–10',
  },
  {
    title: 'Document',
    description:
      'Write the usage guidance while the decisions are fresh, and wire the docs to the source.',
    duration: 'Week 11',
  },
  {
    title: 'Hand over',
    description:
      'Contribution guide, review checklist, and a session on how to add the twenty-first component.',
    duration: 'Week 12',
  },
]

export interface TimelineEvent {
  year: string
  title: string
  description: string
}

export const timelineEvents: TimelineEvent[] = [
  {
    year: '2021',
    title: 'One repository, four libraries',
    description:
      'Four product teams, four component sets, four opinions about what "medium" meant.',
  },
  {
    year: '2022',
    title: 'The first token pass',
    description: 'Colour and spacing consolidated. Everything else stayed where it was.',
  },
  {
    year: '2023',
    title: 'Primitives extracted',
    description:
      'Twenty components, one focus treatment, one control height. Adoption became easier than resistance.',
  },
  {
    year: '2024',
    title: 'Sections and patterns',
    description: 'Marketing stopped rebuilding heroes and started composing them.',
  },
  {
    year: '2025',
    title: 'Density as an axis',
    description: 'Admin tools and marketing pages finally shared a component set.',
  },
  {
    year: '2026',
    title: 'Foundry 1.0',
    description: 'Four levels, one system, documented against its own source.',
  },
]

export interface CaseStudy {
  slug: string
  client: string
  title: string
  summary: string
  sector: string
  year: string
  metrics: Array<{ label: string; value: string }>
}

export const caseStudies: CaseStudy[] = [
  {
    slug: 'northwind-console',
    client: 'Northwind',
    title: 'One console for eleven internal tools',
    summary:
      'Eleven admin surfaces, each with its own table implementation, consolidated into a single shell with a shared data layer.',
    sector: 'Logistics',
    year: '2025',
    metrics: [
      { label: 'Tools consolidated', value: '11' },
      { label: 'Bundle reduction', value: '61%' },
      { label: 'Time to new screen', value: '2 days' },
    ],
  },
  {
    slug: 'halcyon-storefront',
    client: 'Halcyon',
    title: 'A storefront that loads on a train',
    summary:
      'Rebuilt a client-rendered catalogue as server components with small islands, targeting the worst connection the team could find.',
    sector: 'Retail',
    year: '2025',
    metrics: [
      { label: 'JS shipped', value: '−78%' },
      { label: 'LCP, 3G', value: '1.4s' },
      { label: 'Conversion', value: '+9%' },
    ],
  },
  {
    slug: 'meridian-docs',
    client: 'Meridian',
    title: 'Documentation that cannot go stale',
    summary:
      'Wired the documentation site to the component source so every example is generated from the file it documents.',
    sector: 'Developer tools',
    year: '2024',
    metrics: [
      { label: 'Stale examples', value: '0' },
      { label: 'Docs PRs / month', value: '×3' },
      { label: 'Support tickets', value: '−34%' },
    ],
  },
  {
    slug: 'kestrel-onboarding',
    client: 'Kestrel',
    title: 'Onboarding in four steps instead of nine',
    summary:
      'Reduced a nine-screen signup to four, with real validation, resumable progress and a working keyboard path throughout.',
    sector: 'Fintech',
    year: '2024',
    metrics: [
      { label: 'Steps removed', value: '5' },
      { label: 'Completion', value: '+27%' },
      { label: 'Support contacts', value: '−41%' },
    ],
  },
  {
    slug: 'lumen-rebrand',
    client: 'Lumen Works',
    title: 'A rebrand in one pull request',
    summary:
      'Because every surface resolved from tokens, a full visual rebrand touched one file and shipped in a single review.',
    sector: 'Media',
    year: '2023',
    metrics: [
      { label: 'Files changed', value: '1' },
      { label: 'Screens updated', value: '240+' },
      { label: 'Regressions', value: '0' },
    ],
  },
]

export interface Post {
  slug: string
  title: string
  excerpt: string
  category: string
  date: string
  readingTime: string
  author: string
}

export const posts: Post[] = [
  {
    slug: 'density-as-an-axis',
    title: 'Density belongs in your token system',
    excerpt:
      'Most design systems treat compact mode as a table prop. Making it a theme axis is what finally lets admin tools and marketing pages share components.',
    category: 'Design systems',
    date: '2026-03-12',
    readingTime: '8 min',
    author: 'Priya Raman',
  },
  {
    slug: 'manual-tab-activation',
    title: 'Why your tabs should not auto-activate',
    excerpt:
      'Automatic activation feels responsive and quietly punishes keyboard users. A short argument for the boring default.',
    category: 'Accessibility',
    date: '2026-02-27',
    readingTime: '6 min',
    author: 'Tomas Lindqvist',
  },
  {
    slug: 'documentation-from-source',
    title: 'Generate documentation from the file, not about it',
    excerpt:
      'A build step that reads the real component and a test that fails on drift removes an entire category of wrong documentation.',
    category: 'Tooling',
    date: '2026-02-09',
    readingTime: '5 min',
    author: 'Sofia Delgado',
  },
  {
    slug: 'server-components-marketing',
    title: 'Marketing pages do not need a framework runtime',
    excerpt:
      'What actually needs to be interactive on a landing page, and how small the remaining island can be.',
    category: 'Performance',
    date: '2026-01-22',
    readingTime: '9 min',
    author: 'Amara Osei',
  },
  {
    slug: 'variants-that-earn-their-place',
    title: 'A variant should represent a decision, not a padding value',
    excerpt:
      'How to tell a real variant from an inflated catalogue, and why one hundred good components beat three hundred similar ones.',
    category: 'Design systems',
    date: '2026-01-08',
    readingTime: '7 min',
    author: 'Elena Rossi',
  },
  {
    slug: 'focus-return',
    title: 'The half of focus management everyone forgets',
    excerpt:
      'Trapping focus in a dialog is the easy part. Putting it back where it came from is what keyboard users actually notice.',
    category: 'Accessibility',
    date: '2025-12-15',
    readingTime: '4 min',
    author: 'Tomas Lindqvist',
  },
]

export interface Integration {
  name: string
  category: string
  description: string
}

export const integrations: Integration[] = [
  {
    name: 'Vector',
    category: 'Analytics',
    description: 'Event streaming with a typed schema registry.',
  },
  {
    name: 'Cinder',
    category: 'Monitoring',
    description: 'Traces, logs and alerts in one timeline.',
  },
  {
    name: 'Postmark',
    category: 'Email',
    description: 'Transactional delivery with per-template metrics.',
  },
  { name: 'Harbour', category: 'Storage', description: 'Object storage with signed upload URLs.' },
  {
    name: 'Sentinel',
    category: 'Security',
    description: 'Dependency and secret scanning on every push.',
  },
  { name: 'Ledger', category: 'Billing', description: 'Usage metering and invoice generation.' },
  {
    name: 'Relay',
    category: 'Messaging',
    description: 'Webhooks with automatic retry and replay.',
  },
  {
    name: 'Atlas',
    category: 'Search',
    description: 'Typo-tolerant search with faceted filtering.',
  },
  { name: 'Beacon', category: 'Support', description: 'Shared inbox with conversation routing.' },
  { name: 'Quarry', category: 'Data', description: 'Warehouse sync with incremental models.' },
  { name: 'Pilot', category: 'Deployment', description: 'Preview environments per pull request.' },
  { name: 'Lantern', category: 'Documentation', description: 'Docs generated from typed schemas.' },
]

export interface Product {
  slug: string
  name: string
  category: string
  priceCents: number
  compareAtCents?: number
  description: string
  material: string
  badge?: string
  inStock: boolean
  rating: number
  reviews: number
}

export const products: Product[] = [
  {
    slug: 'field-shell-jacket',
    name: 'Field Shell Jacket',
    category: 'outerwear',
    priceCents: 32800,
    compareAtCents: 41000,
    description:
      'A three-layer shell cut close enough to wear in a city and sealed well enough not to.',
    material: 'Recycled ripstop',
    badge: 'Last season pricing',
    inStock: true,
    rating: 4.7,
    reviews: 214,
  },
  {
    slug: 'quarry-overshirt',
    name: 'Quarry Overshirt',
    category: 'outerwear',
    priceCents: 18500,
    description: 'Heavyweight cotton twill that behaves like a jacket and packs like a shirt.',
    material: 'Organic cotton twill',
    inStock: true,
    rating: 4.5,
    reviews: 132,
  },
  {
    slug: 'meridian-knit',
    name: 'Meridian Merino Knit',
    category: 'knitwear',
    priceCents: 14900,
    description: 'Fine-gauge merino with a high neck and no branding anywhere on it.',
    material: 'Extrafine merino',
    badge: 'Restocked',
    inStock: true,
    rating: 4.8,
    reviews: 341,
  },
  {
    slug: 'harbour-cardigan',
    name: 'Harbour Cardigan',
    category: 'knitwear',
    priceCents: 21000,
    description: 'A boxy cardigan with horn buttons and a shawl collar that holds its shape.',
    material: 'Lambswool',
    inStock: false,
    rating: 4.4,
    reviews: 88,
  },
  {
    slug: 'atlas-chino',
    name: 'Atlas Chino',
    category: 'trousers',
    priceCents: 12800,
    compareAtCents: 15900,
    description: 'A straight-leg chino with a proper waistband and pockets that hold a phone.',
    material: 'Compact cotton',
    inStock: true,
    rating: 4.6,
    reviews: 507,
  },
  {
    slug: 'ironwood-trouser',
    name: 'Ironwood Wool Trouser',
    category: 'trousers',
    priceCents: 24500,
    description: 'Pleated, cuffed, and cut for people who sit down during the day.',
    material: 'Wool flannel',
    inStock: true,
    rating: 4.3,
    reviews: 76,
  },
  {
    slug: 'lumen-tote',
    name: 'Lumen Tote',
    category: 'accessories',
    priceCents: 9800,
    description: 'Waxed canvas with a leather base and a strap long enough for a coat.',
    material: 'Waxed canvas',
    badge: 'Best seller',
    inStock: true,
    rating: 4.9,
    reviews: 623,
  },
  {
    slug: 'kestrel-cap',
    name: 'Kestrel Six-Panel Cap',
    category: 'accessories',
    priceCents: 5200,
    description: 'Unstructured, cotton-lined, with a brim that survives being sat on.',
    material: 'Cotton canvas',
    inStock: true,
    rating: 4.2,
    reviews: 195,
  },
  {
    slug: 'verdant-tee',
    name: 'Verdant Heavy Tee',
    category: 'basics',
    priceCents: 4800,
    description: 'A 240gsm tee that holds its collar past the first wash.',
    material: 'Organic cotton',
    inStock: true,
    rating: 4.6,
    reviews: 1102,
  },
  {
    slug: 'halcyon-socks',
    name: 'Halcyon Ribbed Socks',
    category: 'basics',
    priceCents: 2200,
    description: 'Three pairs. Ribbed, reinforced, and the same length as each other.',
    material: 'Merino blend',
    inStock: true,
    rating: 4.5,
    reviews: 289,
  },
]

export const productCategories = [
  { slug: 'outerwear', name: 'Outerwear', description: 'Shells, overshirts and coats.' },
  { slug: 'knitwear', name: 'Knitwear', description: 'Merino, lambswool and cotton knits.' },
  { slug: 'trousers', name: 'Trousers', description: 'Chinos, wool and workwear cuts.' },
  { slug: 'accessories', name: 'Accessories', description: 'Bags, caps and small goods.' },
  { slug: 'basics', name: 'Basics', description: 'Tees, socks and everyday layers.' },
]

export interface MenuDish {
  name: string
  description: string
  price: string
  tags?: string[]
}

export interface MenuCourse {
  course: string
  note?: string
  dishes: MenuDish[]
}

export const restaurantMenu: MenuCourse[] = [
  {
    course: 'To begin',
    note: 'Served from opening until close.',
    dishes: [
      {
        name: 'Cured trout, fennel, dill oil',
        description: 'Two-day cure, shaved fennel, dill pressed the same morning.',
        price: '16',
        tags: ['GF'],
      },
      {
        name: 'Charred leek, hazelnut, aged sheep cheese',
        description: 'Leeks over embers until the outer layer gives up entirely.',
        price: '14',
        tags: ['V'],
      },
      {
        name: 'Sourdough, cultured butter',
        description: 'Four-day starter, butter churned in-house on Tuesdays.',
        price: '7',
        tags: ['V'],
      },
    ],
  },
  {
    course: 'Mains',
    dishes: [
      {
        name: 'Aged duck breast, quince, bitter leaves',
        description: 'Dry-aged fourteen days, quince from the orchard at Ashfield.',
        price: '34',
      },
      {
        name: 'Turbot on the bone, brown butter, capers',
        description: 'Whole fish for two, carved at the table.',
        price: '58',
        tags: ['For two'],
      },
      {
        name: 'Barley, wild mushroom, preserved lemon',
        description: 'Slow-cooked barley with a mushroom broth reduced for six hours.',
        price: '26',
        tags: ['V', 'Vegan on request'],
      },
    ],
  },
  {
    course: 'To finish',
    dishes: [
      {
        name: 'Burnt honey tart, crème fraîche',
        description: 'Honey taken just past the point most people would stop.',
        price: '12',
        tags: ['V'],
      },
      {
        name: 'Poached pear, walnut, blue cheese',
        description: 'Pears poached in the last of the autumn cider.',
        price: '13',
        tags: ['V'],
      },
      {
        name: 'Selection of British cheese',
        description: 'Three cheeses, oat crackers, quince paste.',
        price: '16',
        tags: ['V'],
      },
    ],
  },
]

export const changelog = [
  {
    version: '1.0.0',
    date: '2026-03-14',
    title: 'Foundry 1.0',
    summary:
      'The first complete release: four composition levels, one token system, one catalogue.',
    changes: [
      {
        kind: 'added' as const,
        text: '45 primitives across actions, forms, feedback, overlay, navigation, data and layout.',
      },
      {
        kind: 'added' as const,
        text: '15 navigation patterns, 12 header systems and 10 footer systems.',
      },
      {
        kind: 'added' as const,
        text: '28 form flows with real validation, first-invalid focus and live error announcement.',
      },
      {
        kind: 'added' as const,
        text: '112 sections spanning marketing, commerce, SaaS, content, application and state.',
      },
      {
        kind: 'added' as const,
        text: '10 complete page patterns and 10 multi-route starter products.',
      },
      {
        kind: 'added' as const,
        text: 'Theme playground with five palettes and three densities, persisted and flash-free.',
      },
    ],
  },
  {
    version: '0.9.0',
    date: '2026-02-20',
    title: 'Starters and the composition playground',
    summary: 'Level 4 arrives, and sections become assemblable rather than merely browsable.',
    changes: [
      {
        kind: 'added' as const,
        text: 'Ten starter products, each with working navigation and its own 404 path.',
      },
      { kind: 'added' as const, text: 'URL-driven section composition playground.' },
      {
        kind: 'changed' as const,
        text: 'Catalogue records gained `relatedItems`, validated to resolve.',
      },
      {
        kind: 'fixed' as const,
        text: 'Drawer no longer restored focus to a detached element after route changes.',
      },
    ],
  },
  {
    version: '0.8.0',
    date: '2026-01-30',
    title: 'Density becomes an axis',
    summary: 'Compact, Default and Relaxed move out of component props and into the token layer.',
    changes: [
      {
        kind: 'added' as const,
        text: 'Density tokens for control height, padding, stack rhythm and row height.',
      },
      {
        kind: 'changed' as const,
        text: 'Every control now derives its height from `--density-control-height`.',
      },
      {
        kind: 'removed' as const,
        text: 'Per-component `compact` props, superseded by the density axis.',
      },
    ],
  },
  {
    version: '0.7.0',
    date: '2026-01-12',
    title: 'Source registry',
    summary: 'Documentation is generated from the files it documents.',
    changes: [
      {
        kind: 'added' as const,
        text: 'Build-time source registry; the viewer reads the real file.',
      },
      {
        kind: 'added' as const,
        text: 'Drift test that regenerates the registry and fails on mismatch.',
      },
      {
        kind: 'fixed' as const,
        text: 'Copy button now falls back to a hidden textarea on insecure origins.',
      },
    ],
  },
  {
    version: '0.6.0',
    date: '2025-12-04',
    title: 'Accessibility pass',
    summary: 'Focus management and live regions moved into the primitives.',
    changes: [
      {
        kind: 'added' as const,
        text: 'Focus trap with guaranteed focus return across Dialog, Drawer and the palette.',
      },
      { kind: 'changed' as const, text: 'Tabs switched to manual activation.' },
      { kind: 'fixed' as const, text: 'Tooltips now open on keyboard focus, not only on hover.' },
      {
        kind: 'fixed' as const,
        text: 'Toast queue announces in order instead of interrupting itself.',
      },
    ],
  },
]

export const faqShort = faqs.slice(0, 4)

Demo source — adapt to your project. Foundry is not published as a package.

Usage

When the answers are short, hiding them behind a click costs more than it saves — and makes the page unsearchable with Ctrl+F, which is how people actually use an FAQ.

  • Use this when answers are under fifty words; longer and the page becomes a wall.
  • Keep the escape-hatch link visible at the top of the column, not buried at the bottom.

Variants and states

Every entry below is a genuine difference in behaviour or layout, and every one of them is visible in the preview above.

  • Six questions
  • Two columns at sm
  • Ask-something-else link

Accessibility

Definition list
Questions and answers are programmatically paired.
No hidden content
Nothing depends on JavaScript to be readable.

Foundry implements published ARIA patterns and is tested against them. No WCAG certification is claimed — see the accessibility documentation for what is and is not covered.