Github

Accordion

Native <details> / <summary>. Leave items unnamed to allow several open. Give them the same name to keep only one open (the previous panel closes). Height animates with ::details-content when the browser supports it. There is no wrapper. Exclusive open is the name attribute plus the short toggle handler in the snippet.

Several open

Is it accessible?
Yes. It uses native details and summary.
Can several stay open?
Yes. With no name, each item is independent.
Is it styled?
Yes. Vega chrome on top of the disclosure.
<div class="accordion">
	<details class="accordion-item" open>
		<summary class="accordion-trigger">Is it accessible?</summary>
		<div class="accordion-content">Yes. It uses native details and summary.</div>
	</details>
	<details class="accordion-item" open>
		<summary class="accordion-trigger">Can several stay open?</summary>
		<div class="accordion-content">Yes. With no name, each item is independent.</div>
	</details>
	<details class="accordion-item">
		<summary class="accordion-trigger">Is it styled?</summary>
		<div class="accordion-content">Yes. Vega chrome on top of the disclosure.</div>
	</details>
</div>

One at a time

One at a time?
Give every item the same name. The browser closes the previous one. A few lines of toggle code cover older browsers.
Does it animate?
The chevron always rotates. Panel height uses ::details-content where the browser allows it.
Can I close the last one?
Yes. Clicking the open summary closes it, so none need stay open.
<script>
	function exclusive(event) {
		const item = event.currentTarget;
		if (!item.open || !item.name) return;
		const root = item.closest('.accordion');
		for (const other of root.querySelectorAll('details[name="' + CSS.escape(item.name) + '"]')) {
			if (other !== item) other.open = false;
		}
	}
</script>

<div class="accordion">
	<details class="accordion-item" name="faq" open ontoggle={exclusive}>
		<summary class="accordion-trigger">One at a time?</summary>
		<div class="accordion-content">
			Give every item the same name. The browser closes the previous one.
			The toggle handler covers browsers that ignore name.
		</div>
	</details>
	<details class="accordion-item" name="faq" ontoggle={exclusive}>
		<summary class="accordion-trigger">Does it animate?</summary>
		<div class="accordion-content">
			Chevron always. Height uses ::details-content where the browser allows it.
		</div>
	</details>
</div>