Accordions with <details>

<details> demos and exploration of html accordions.

Basic Accordion with <details>

Details

This is just a basic accordion

<details>
	<summary>Details</summary>
	<p>This is just a basic accordion</p>
</details>

Animating the caret

<style>
	::marker,
	::-webkit-details-marker {
		display: none;
	}

	summary {
		list-style: none;
	}

	details summary:before {
		content: '';
		display: inline-block;
		rotate: 0deg;
		margin-inline-end: 0.5em;
		transition: rotate 0.3s ease-in-out;
	}

	details[open] > summary:before {
		rotate: 90deg;
	}
</style>

A CSS Only Accordion with <details>

It feels like this should work and maybe it’s just a browser issue / quirk that it doesn’t currently. Maybe by the time you’re looking at this it’s ✨flawless✨.

Details

This is just a basic accordion

<details class="css-only">
	<summary>Details</summary>
	<div id="demo-accordion-css">
		<p>This is just a basic accordion</p>
	</div>
</details>

<style>
	.css-only #demo-accordion-css {
		transition:
			display 0.3s allow-discrete,
			opacity 0.3s,
			max-block-size 0.3s;
		opacity: 0;
		max-block-size: 0;
	}

	.css-only[open] #demo-accordion-css {
		opacity: 1;
		max-block-size: 200px;
	}

	@starting-style {
		.css-only[open] #demo-accordion-css {
			opacity: 0;
			max-block-size: 0;
		}
	}
</style>

WTF is max-block-size

max-block-size === max-height. Well.. kinda, it’s the logical property version of max-height. This supports all type directions instead of just top to bottom, left to right. This is the same as things like margin-block, margin-inline. It has full browser support, so update your brain matter and use block-size and inline-size.


Animate According with JavaScript via View Transitions

Woof, no wonder we reach for JS frameworks. It’s not too crazy but it’s a lot of JS just to animate an accordion. Remember .slideDown();.

This next one is good, less code, but the container itself still isn’t sliding without adding in a custom JS height animation.

Details

This is an accordion with more

I'm adding more stuff here to make animation look nicer.

<script>
	const details = document.querySelector('#demo-accordion-vt');
	const content = details.querySelector('#demo-accordion-vt .details-content');
	const summary = details.querySelector('#demo-accordion-vt summary');

	summary.addEventListener('click', async (e) => {
		e.preventDefault();
		let transition = document.startViewTransition(() => {
			if (details.open) {
					details.open = false;
			} else {
					details.open = true;
			}
		});
	});
</script>

<details id="demo-accordion-vt">
	<summary>Details</summary>
	<div class="details-content">
		<p>This is an accordion with more</p>
		<p>I'm adding more stuff here to make animation look nicer.</p>
	</div>
</details>

Animate According with JavaScript via WAAPI

Get ready, it’s about to be heavy.

Details

This is an accordion with more

I'm adding more stuff here to make animation look nicer.

<script>
	const details = document.querySelector('#demo-accordion-waapi');
	const content = details.querySelector('#demo-accordion-waapi .details-content');
	const summary = details.querySelector('#demo-accordion-waapi summary');
	let isCollapsing = false;
	let isExpanding = false;
	let animation = null;

	summary.addEventListener('click', (e) => {
	  e.preventDefault();
	  details.style.overflow = 'hidden';
	  details.open ? collapse() : expand();
	});

	function expand() {
	  details.style.height = `${details.offsetHeight}px`;
	  details.open = true;

	  window.requestAnimationFrame(() => {
	    isExpanding = true;
	    if (animation) animation.cancel();

	    const start = `${details.offsetHeight}px`;
	    const end = `${summary.offsetHeight + content.offsetHeight}px`;
	    animation = details.animate({ height: [start, end] }, { duration: 300, easing: 'ease-in-out' });

	    animation.onfinish = () => onFinish(true);
	    animation.oncancel = () => (isExpanding = false);
	  });
	}

	function collapse() {
	  isCollapsing = true;

	  window.requestAnimationFrame(() => {
	    if (animation) animation.cancel();

	    const start = `${details.offsetHeight}px`;
	    const end = `${summary.offsetHeight}px`;
	    animation = details.animate({ height: [start, end] }, { duration: 300, easing: 'ease-in-out' });

	    animation.onfinish = () => onFinish(false);
	    animation.oncancel = () => (isCollapsing = false);
	  });
	}

	function onFinish(isOpen) {
	  details.open = isOpen;
	  animation = null;
	  isCollapsing = false;
	  isExpanding = false;
	  details.style.height = null;
	  details.style.overflow = 'visible';
	}
</script>

<details id="demo-accordion-waapi">
	<summary>Details</summary>
	<div class="details-content">
		<p>This is an accordion with more</p>
		<p>I'm adding more stuff here to make animation look nicer.</p>
	</div>
</details>

Exclusive Accordions

By giving an accordion a name attribute, you can make it part of a group of “exclusive” accordions where only one with the same name can be open at a time.

Q: Will only one of these stay open at a time?

You betcha

Q: Why doesn't starting-style and allow-discrete work here?

Dunno, it probably should tbh.

Q: Why is there a third question?

Just here so I don't get fired.

<details name="faq">
	<summary>Q: Will only one of these stay open at a time?</summary>
	<p>You betcha</p>
</details>
<details name="faq">
	<summary>Q: Why doesn't starting-style and allow-discrete work here?</summary>
	<p>Dunno, it probably should tbh.</p>
</details>
<details name="faq">
	<summary>Q: Why is there a third question?</summary>
	<p>Just here so I don't get fired.</p>
</details>