Accordion

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

What is sitelo?
A static site generator built on Vite. Pages are functions that return HTML.
Does it ship a runtime?
No. Nothing reaches the browser unless you link a script yourself.
Can I use TypeScript?
Yes — .ht.ts and .ht.tsx are page extensions like any other.
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

Open on arrival
This one has open: true.
Closed
This one does not.
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.

First
Open another and this one closes.
Second
And so does this one.
Third
Only one is ever open.
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.

Install

Add the package and its markup companion:

npm install sitelo javascript-to-html
Configure

Optional. Vite options live under the vite key.

sitelo.config.js
Deploy

Publish the output directory to any static host.

NetlifyVercelCloudflare PagesGitHub Pages
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.

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 (() => {
  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():

PropTypeDefaultDescription
itemsArray[]Strings, or { title, content, open } objects.
namestringA shared name makes the sections mutually exclusive.

accordionItem():

PropTypeDefaultDescription
titleChildThe summary line.
openbooleanfalseWhether it starts expanded.
namestringSame effect as on the parent, when building items by hand.