# Accordion

## Installation

The open state is `<details>` and `<summary>`, so a row opens before any
JavaScript runs and keeps working if none ever does. A delegated runtime binds
on top to add what the element has no answer for — arrow-key roving focus,
`multiple`, `defaultValue`, and an `accordion:change` event. There is no client
directive to write; it binds itself and survives a view transition.

## Preview

      <h3>Does this need JavaScript?</h3>
      No. The disclosure is the browser's, and so is the exclusive-group behaviour when items share a `name`.

      <h3>Where do the styles come from?</h3>
      Your application's TeaCSS build. The package ships no stylesheet — `accordionRecipe` names the classes and your entry generates them.

      <h3>Can I restyle it?</h3>
      Every part takes `class`, and caller classes merge last.

```astro
---
import { Accordion, AccordionContent, AccordionItem, AccordionSummary } from "@teasim/astro/accordion";
---

<Accordion>
  <AccordionItem name="faq" open>
    <AccordionSummary><h3>Does this need JavaScript?</h3></AccordionSummary>
    <AccordionContent>No. The disclosure is the browser's.</AccordionContent>
  </AccordionItem>
  <AccordionItem name="faq">
    <AccordionSummary><h3>Where do the styles come from?</h3></AccordionSummary>
    <AccordionContent>Your application's TeaCSS build.</AccordionContent>
  </AccordionItem>
</Accordion>
```

## Examples

### One at a time

Give sibling Items the same non-empty `name` and the browser closes the open
one when another opens. The group name is **document-wide**, so a page with two
independent accordions needs two names.

      <h4>Green</h4>
      Two minutes, water just off the boil.

      <h4>Black</h4>
      Three minutes, water at a rolling boil.

Open at most one member of a named group initially — two Items sharing a name
and both carrying `open` is a state the browser has to resolve, and it closes
one of them for you.

### Several at once

Dropping `name` is not enough. `multiple` defaults to `false`, and on bind the
runtime keeps the first open Item and closes the rest — so two Items that both
carry `open` render as one open row. Pass `multiple` on the **root** to keep
them all.

      <h4>Ships no stylesheet</h4>
      The application's TeaCSS build generates every rule.

      <h4>Ships no client JavaScript</h4>
      For this family the open state is native; the runtime only adds focus and events.

```astro
<Accordion multiple>
  <AccordionItem open>…</AccordionItem>
  <AccordionItem open>…</AccordionItem>
</Accordion>
```

The two mechanisms are independent and both apply. `name` is the browser's,
and it works with scripting off; `multiple` is the runtime's, and it wins on
bind. Setting `multiple` while siblings share a `name` gets you the browser's
exclusivity anyway — use one or the other, not both.

## API reference

Four components, each rendering one native element.

| Component | Renders | Own props |
| --- | --- | --- |
| `Accordion` | `<div>` | `multiple`, `defaultValue`, `collapsible`, `orientation`, `loopFocus` |
| `AccordionItem` | `<details>` | `value` — plus native `open` and `name` |
| `AccordionSummary` | `<summary>` | none |
| `AccordionContent` | `<div>` | none |

### Accordion

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `multiple` | `boolean` | `false` | Several Items open at once |
| `defaultValue` | `string \| string[]` | — | Which `value` starts open; an array joins with commas |
| `collapsible` | `boolean` | `true` | The last open Item may close |
| `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | Which arrow keys move focus |
| `loopFocus` | `boolean` | `true` | Focus wraps at either end |

### AccordionItem

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `value` | `string` | — | Identifies the Item to `defaultValue` |
| `open` | `boolean` | `false` | Native `<details>` attribute |
| `name` | `string` | — | Native group name; siblings sharing one are mutually exclusive |

`AccordionSummary` must be the **first direct element child** of its Item —
that is a `<details>` requirement, not this component's. `AccordionContent`
renders a plain `<div>` and does not force a region landmark.

Each component owns `aria-controls`, `aria-disabled`, `aria-expanded` and
`role`, and `AccordionSummary` owns `tabindex` as well; passing any of them is
ignored rather than merged. Everything else native passes through.

### How the props reach the runtime

The five root props are not read by the server half. `Accordion` writes each as
a `data-*` attribute — `data-multiple`, `data-default-value`,
`data-collapsible`, `data-orientation`, `data-loop-focus` — and the runtime
reads them from the DOM on bind. That is why the markup is complete before any
script runs — the options travel as markup, not as a hydration payload.

The runtime itself is **not public API**. `@teasim/astro/accordion` exports the
four components and nothing else; the browser half is loaded by the component's
own `<script>`, which is the single edge between the two halves. Drive a bound
Accordion through events instead:

```ts
const root = document.querySelector('[data-slot="accordion/root"]');

// Listen: fires on the root whenever the open set changes.
root.addEventListener("accordion:change", (event) => {
  event.detail.value; // string[] — the open Items' values
});

// Drive: dispatch on the root, not on an Item.
root.dispatchEvent(new CustomEvent("accordion:set", { detail: { value: ["shipping"] } }));
```

Both carry `{ value }` rather than a bare array, and `accordion:set` is ignored
unless its target is the root itself. An Item needs a `value` for either
direction to name it.

### accordionRecipe

`accordionRecipe` exposes `root`, `item`, `summary`, and `content`. It lives on
`@teasim/astro/recipes`, never on the family subpath, so rendering an Accordion
never pulls a recipe in behind it.

```ts
import { accordionRecipe } from "@teasim/astro/recipes";

const classes = accordionRecipe();
classes.item({ className: "border-color:gray-600" });
```