Files
Htmx/docs/Components/Accordion.md
T
2026-05-05 23:55:26 +05:00

4.8 KiB

Accordion

An expand/collapse panel list — like a FAQ section or a step-by-step guide where the user reveals each answer one at a time.


Quick example

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 for the Tailwind build step."),
    })

That's it. Drop this into a page slot and you have a working FAQ section.


All the options

public Accordion(
    string id,
    IEnumerable<(string Title, string Content)> items,
    int openIndex = -1)
Parameter What it does
id A unique identifier for this accordion on the page. If you have two accordions on the same page, they need different ids.
items The list of panels. Each item is a pair: the header text (Title) and the body content (Content).
openIndex Which panel should start open. 0 = first panel, 1 = second, -1 = all closed (default).

Real-world examples

FAQ page with the first item pre-opened

new Accordion(
    id: "faq",
    items: new[]
    {
        ("How do I reset my password?", "Go to Settings → Security → Reset Password."),
        ("How do I cancel my account?", "Contact support from the Help page."),
        ("Where are my invoices?",      "Under Billing in your account dashboard."),
    },
    openIndex: 0)   // first answer visible on load

Step-by-step guide with HTML content inside items

Item Content is rendered as raw HTML, so you can use markup inside it:

new Accordion(
    id: "setup-guide",
    items: new[]
    {
        ("Step 1 — Install dependencies",
         "Run <code>npm install</code> inside the <code>Htmx.ApiDemo</code> folder."),
        ("Step 2 — Start MongoDB",
         "<p>Start the MongoDB service, then confirm it is running on <code>localhost:27017</code>.</p>"),
        ("Step 3 — Run the app",
         "Run <code>dotnet run --project Htmx.ApiDemo</code> and open <code>http://localhost:5120</code>."),
    },
    openIndex: 0)

Important: Title and Content are inserted as raw HTML. If either value comes from user input or a database, HTML-encode it first:

System.Web.HttpUtility.HtmlEncode(userTitle)

Inside a page

<!-- Templates/FaqPage.htmx -->
<div class="max-w-2xl mx-auto py-10">
  <h1 class="text-2xl font-bold mb-6">Frequently Asked Questions</h1>
  $$FaqAccordion$$
</div>
// Templates/FaqPage.htmx.cs
public sealed class FaqPage : FaqPageBase
{
    private readonly IHtmxComponent _faqAccordion;

    public FaqPage()
    {
        _faqAccordion = new Components.Accordion(
            id: "faq",
            items: new[]
            {
                ("What is BeepBoop?", "A fast, AOT-safe HTMX web framework built on .NET 10."),
                ("Do I need Node.js?", "Only to run the Tailwind CSS build step."),
                ("Is MongoDB required?", "No — swap in any data store you prefer."),
            });
    }

    protected override void RenderFaqAccordion(HtmxRenderContext ctx)
        => _faqAccordion.Render(ctx.Next());
}

How it works

The server renders all panels into the HTML. Closed panels are given height: 0; opacity: 0 inline styles so they are invisible immediately — no layout flash. The JavaScript in components.js (initAccordion) then attaches click listeners.

When a panel is opened, JS reads the panel's scrollHeight (its natural height) and animates the inline height from 0 to that value alongside the opacity, giving a smooth slide-down. The chevron icon rotates 180° to point down when open.

If the page content is updated by HTMX, htmx:afterSwap re-runs the initialisation so newly swapped-in accordions also get click behaviour.

Users can open multiple panels simultaneously — there is no "only one open at a time" constraint.


Tips

  • The id must be unique if you place more than one accordion on a page.

  • openIndex only controls the initial server-rendered state — the user can freely open or close any panel after that.

  • To listen for accordion interactions from another script, add a click listener to the parent container and check event.target.closest('.accordion-trigger'). ("How do I deploy?", "Run 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<IResult> HandleAsync(
        Query _,
        HttpContext ctx,
        CancellationToken ct)
        => ctx.WriteHtmxPage(new FaqPage(), title: "FAQ");
}