# Accordion
An expand/collapse panel list. Items are collapsed by default; one item can be pre-expanded at server render time. Client-side toggle is handled by `components.js`.
---
## HTML structure
```
div.accordion-root[id] ← outer wrapper
div.accordion-item ← one per item, border-b separator
h3
button.accordion-trigger ← clickable header; aria-expanded tracks state
{title text}
svg.accordion-chevron ← rotates 180° when open
div.accordion-panel ← collapsible area; height/opacity driven by JS
div.pb-4
{content}
```
---
## CSS mechanics
| Class / property | Effect |
|---|---|
| `overflow-hidden` on panel | Prevents content leaking outside the panel during animation |
| `transition-all duration-200` on panel | Smooth height and opacity animation |
| `height: 0; opacity: 0` (collapsed) | Starting state set server-side for closed items |
| `height: auto; opacity: 1` (open) | Starting state for the pre-expanded item |
| `accordion-chevron` + JS `rotate(180deg)` | Chevron rotates down when expanded |
---
## JavaScript (`initAccordion` in `components.js`)
Runs on `DOMContentLoaded` and on `htmx:afterSwap` so HTMX-swapped accordions are correctly initialized.
**Per-instance initialization:**
1. Guard `root._accInitialised` prevents double-binding after re-renders
2. For each `.accordion-trigger`, attach a `click` listener:
- Read current state from `aria-expanded`
- If currently open → set `panel.style.height = "0"`, `opacity = "0"`, `aria-expanded = "false"`
- If currently closed → set `panel.style.height = scrollHeight + "px"`, `opacity = "1"`, `aria-expanded = "true"`
3. Rotate `.accordion-chevron` via `transform: rotate(180deg)` when open
---
## Constructor signature
```csharp
public Accordion(
string id,
IEnumerable<(string Title, string Content)> items,
int openIndex = -1)
```
| Parameter | Description |
|---|---|
| `id` | Unique element id for the root `div` |
| `items` | List of `(Title, Content)` tuples |
| `openIndex` | Zero-based index of the pre-expanded item; `-1` = all closed |
---
## Usage examples
### All closed
```csharp
new Accordion(
id: "faq",
items: new[]
{
("What is this?", "A fast HTMX app framework."),
("Is it AOT-safe?", "Yes, fully."),
("Do I need Node?", "Only to run the Tailwind build step."),
})
```
### One pre-expanded
```csharp
new Accordion(
id: "setup-steps",
items: new[]
{
("Step 1 — Install", "Run npm install in the project folder."),
("Step 2 — Configure", "Edit appsettings.json with your connection string."),
("Step 3 — Run", "Use dotnet run to start the server."),
},
openIndex: 0)
```
### HTML content in items
```csharp
new Accordion(
id: "code-examples",
items: new[]
{
("C# snippet", "
var x = 1 + 1;"),
("Tip", "Use AOT-safe serialization patterns.
"), }) ``` --- ## Tips and tricks - `openIndex` only controls the initial server-rendered state. After the page loads, the user can open/close any item freely. - Item `Title` and `Content` strings are inserted as raw HTML — HTML-encode any user-supplied values before passing them in. - Multiple items can be opened simultaneously by the user — there is no "only one open at a time" constraint in the JS. - If you need to identify which accordion is which after a click, listen to the parent element and inspect `event.target.closest('.accordion-item')`. - The `id` must be unique on the page if you place more than one accordion. --- ## Complete page example **`Templates/FaqPage.htmx`** ```htmlEverything you need to know about BeepBoop.
$$FaqAccordion$$dotnet publish -c Release for a native AOT binary."),
});
}
protected override void RenderFaqAccordion(HtmxRenderContext ctx)
=> _faq.Render(ctx.Next());
}
```
**`Templates/FaqPage.htmx.cs` — GET handler**
```csharp
[Handler]
[MapGet("/faq")]
public static partial class GetFaqHandler
{
public record Query();
private static Task