Here’s a nice simple demo from Moritz Gießmann on animating the triangle of a <details>
element, which is the affordance that tells people this thing can be opened. Animating it, then is another kind of affordance that tells people this thing is opening now.
The tricks?
- Turn off the default triangle:
details summary::-webkit-details-marker { display:none; }
. You can’t animate that one. - Make a replacement triangle with the CSS border trick and a pseudo element.
- Animate the new triangle when the state is open:
details[open] > summary::before { transform: rotate(90deg); }
.
This only animates the triangle. The content inside still “snaps” open. Wanna smooth things out? Louis Hoebregts’ “How to Animate the Details Element Using WAAPI” covers that.
Here’s a fork where I’ll combine them just because:
I see Moritz put the cursor: pointer;
on the summary
as well like Greg Gibson suggests.
You can also animate the details element with just css.
Less smooth than the JS solution.
But also a nice solution via css only.
The cool thing about the internet is you share something amazing and people will share something amazing back. ❤
Here is the pure css version that works like your final version that uses both css and js.
That’s cool. I didn’t know it yet. Thanks for the work from you and Moritz. stay creative!
Is there a way to animate closing with just CSS?
Yes, with a trick similar to that using a checkbox.
Move the content after the element and use your preferred way of hiding with the sibling selector
details:not([open]) + .content
.Reason:
The element hides its content programmatically. CSS can’t delay that, thus the closing animation happens after the content is hidden.
Further barrier:
Animating
height
requires a pre-calculated value,auto
won’t be animated. If the content’s height is not fixed then JS is required to measure the content and store the height in a CSS variable.A CSS-only solution is still possible by animating other properties.
If the content is absolute positioned then animating
opacity
is a good way to show/hide.If the content is within the document flow then some other trick is necessary. Animating
margin-top
of the content’s children to a big enough negative value can be a solution, albeit inefficient and hacky. But it works. Good for a no-JS fallback and if JS is enabled, it can do properheigth
animation. That however adds more maintenance and testing time.Example:
codepen[yLMmmVb][350]
I also tried to create a CSS-only solution. The closing is not smooth, but it opens somewhat gracefully: https://codepen.io/dada1smo/pen/LYydWBg
Hey! First of all, excellent content. I have two questions regarding the code itself.
I’m implementing it on TypeScript with Angular, and all of these function you wrote would be called inside another function, which is called once one of the details elements are clicked.
They are called inside a forEach, like this:
…
isOpen = () => {
let allDetails = document.querySelectorAll(“details”);
allDetails.forEach(details => {
let summaryContent = details.querySelector(‘summary’) as HTMLElement;
let subContent = details.querySelector(‘.nav_sub_link’) as HTMLElement;
summaryContent.addEventListener(‘click’, (e) => onClick(e));
…
Animation is working, just need some tweaks. But the onClick() function is only called only when i click a details element first, so the ‘else if’ part would be called then.
I’m still a beginner on TypeScript and JavaScript so i’m struggling really hard to find the culprit.
Any suggestions?
ty.
Why do all the CSS only solutions in the comments only work on the first click?
First click is smooth, but after closing and reopening, you only get the effect on the first try?
@David
I noticed the same problem when using the in different browsers.
Firefox: Transitioning opacity on open works every time
Chrome: Transitioning opacity on open works the first
Safari: Transitioning opacity on open never works
I am really confused and haven’t found any solution to this problem yet. I was experimenting with animations instead of transition but I never got a good result in all browsers.
The animation of the triangle does NOT work in Safari version 16 running on macOS Monterey :(((
details tag was mostly created for accessibility reasons. If you use click events you basically kill all the functionalities (e.g. screen readers). You should always use the toggle event that is fired in any case and never use the click event unless you want to target desktop users only.
I’ve found two ways to animate the opening and closing of details element using only CSS, no JavaScript needed. One way uses the adjacent sibling combinator (+) and the other uses the :has() pseudo-class. Both work great and give a smooth animation.
Here is the example of the article with my two approaches: https://codepen.io/jgustavoas/pen/zYLNKbN
The main key is to start the page with the
details
element in its open state (<details open>
), then change itsmax-height
value by toggling a checkbox, and use a transition effect to animate that change.To control the change of the element’s
max-height
value, you will need a checkbox and associate it with the<details>
element. You can choose from two approaches to do this, and both work great:1) Using the
+
adjacent sibling combinator2) Using the
:has()
pseudo-classThe difference between the two approaches is that the checkbox input must come immediately before the
<details>
element in the first approach, and the checkbox must be a descendant of the<details>
element in the second approach.Inside the
<summary>
tag of the<details>
element , place a<label>
that toggles the checkbox:One caveat to consider when using the
:has()
approach is that in Firefox the user must explicitly enable this feature (see caniuse.com and MDN Web Docs).