# Accordion

> Collapsible sections, using the browser’s own <details> — including its exclusive mode.

Each section is a `<details>`. Opening, closing, keyboard support and find-in-page all come from the browser, and the accordion works with JavaScript off — which for an FAQ, the most common use, matters.

## Basic accordion

```javascript
accordion({
  items: [
    { title: 'What is sitelo?', content: 'A static site generator built on Vite. Pages are functions that return HTML.' },
    { title: 'Does it ship a runtime?', content: 'No. Nothing reaches the browser unless you link a script yourself.' },
    { title: 'Can I use TypeScript?', content: 'Yes — .ht.ts and .ht.tsx are page extensions like any other.' },
  ],
})
```

## Open by default

```javascript
accordion({
  items: [
    { title: 'Open on arrival', content: 'This one has open: true.', open: true },
    { title: 'Closed', content: 'This one does not.' },
  ],
})
```

## One at a time

A shared `name` makes the sections mutually exclusive — opening one closes the others. That is the browser’s own behaviour for `<details name>`, not a script.

```javascript
accordion({
  name: 'demo-exclusive',
  items: [
    { title: 'First', content: 'Open another and this one closes.', open: true },
    { title: 'Second', content: 'And so does this one.' },
    { title: 'Third', content: 'Only one is ever open.' },
  ],
})
```

## Rich content

Build the sections with `accordionItem()` when the content is more than a paragraph.

```javascript
accordion(
  accordionItem({ title: 'Install', open: true },
    stack({ gap: 'sm' },
      text({ variant: 'small', tone: 'muted' }, 'Add the package and its markup companion:'),
      code('npm install sitelo javascript-to-html'),
    ),
  ),
  accordionItem({ title: 'Configure' },
    stack({ gap: 'sm' },
      text({ variant: 'small', tone: 'muted' }, 'Optional. Vite options live under the vite key.'),
      code('sitelo.config.js'),
    ),
  ),
  accordionItem({ title: 'Deploy' },
    stack({ gap: 'sm' },
      text({ variant: 'small', tone: 'muted' }, 'Publish the output directory to any static host.'),
      stack({ direction: 'row', gap: 'sm', wrap: true },
        chip({ size: 'sm' }, 'Netlify'),
        chip({ size: 'sm' }, 'Vercel'),
        chip({ size: 'sm' }, 'Cloudflare Pages'),
        chip({ size: 'sm' }, 'GitHub Pages'),
      ),
    ),
  ),
)
```

## An FAQ

The shape this component exists for: content that is already in the HTML, collapsed for scanning, and findable by a search engine because it never left the page.

```javascript
return (() => {
  const faq = [
    ['Is it really zero-config?', 'A project with one file in src/ and no config builds. Everything else is opt-in.'],
    ['How do dynamic routes work?', 'Brackets in filenames. generateStaticParams lists what to build.'],
    ['What about search?', 'Set pagefind: true and the build indexes every page.'],
  ]

  return accordion({
    name: 'demo-faq',
    items: faq.map(([title, content]) => ({ title, content })),
  })
})()
```

## Props

`accordion()`:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | `Array` | `[]` | Strings, or { title, content, open } objects. |
| `name` | `string` | — | A shared name makes the sections mutually exclusive. |

`accordionItem()`:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `Child` | — | The summary line. |
| `open` | `boolean` | `false` | Whether it starts expanded. |
| `name` | `string` | — | Same effect as on the parent, when building items by hand. |
