Accordion
Accordion component for Hono JSX
Examples
What is this?
According component for htsx.
Does it need JavaScript?
No. Powered by <details>/<summary> only.
How do I close all items?
Click the currently open item again.
Feature A
Multiple items can be open at the same time.
Feature B
Pass variant="multiple" to enable this behavior.
Feature C
Use defaultOpen to pre-open specific items.
JSX title
contentaccepts JSX.- Icons, lists, images, buttons, etc.
Another JSX title
Inline elements like bold and italic work too.
// single (default): only one item open at a time, defaultOpen can be set
<Accordion
defaultOpen={["q1"]}
items={[
{
value: "q1",
title: "What is this?",
content: "According component for htsx.",
},
// more items
/>
// multiple: several items can be open at once
<Accordion
variant="multiple"
defaultOpen={["f1", "f3"]}
items={[
{
value: "f1",
title: "Feature A",
content: "Multiple items can be open at the same time.",
},
// more items
]}
/>
// title and content accept JSX
<Accordion
class="rounded-md border border-border bg-card text-card-foreground"
items={[
{
value: "jsx1",
title: (
<span class="flex items-center gap-2">
<Sun />
JSX title
</span>
),
content: (
<ul>
<li>
<code>content</code> accepts JSX.
</li>
<li>Icons, lists, images, buttons, etc.</li>
</ul>
),
},
// more items
]}
/>Code
import type { JSX, Child } from "hono/jsx";
import { c } from "./c";
import { ChevronDown } from "./icons";
export function Accordion({
id,
variant = "single",
defaultOpen = [],
items,
class: custom,
...props
}: JSX.IntrinsicElements["div"] & {
variant?: "single" | "multiple";
defaultOpen?: string[];
items: {
value: string;
title: Child;
content: Child;
}[];
}) {
const groupName = variant === "single" ? (id ?? crypto.randomUUID()) : undefined;
return (
<div id={id} class={c("w-full divide-y divide-border", custom)} {...props}>
{items.map((item) => (
<details
key={item.value}
name={groupName}
open={defaultOpen.includes(item.value) || undefined}
class="group/accordion-item"
>
<summary class="flex cursor-pointer list-none items-center justify-between gap-4 px-4 py-3 font-medium select-none hover:opacity-80">
{item.title}
<span class="shrink-0 text-muted-foreground transition-transform group-open/accordion-item:rotate-180">
<ChevronDown />
</span>
</summary>
<div class="px-4 pb-4 text-muted-foreground">{item.content}</div>
</details>
))}
</div>
);
}export const ChevronDown = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-chevron-down-icon lucide-chevron-down"
>
<path d="m6 9 6 6 6-6" />
</svg>
);