Building an animated <details> element
So why are accordions used at all?
While using accordions or expanders in some situations is a bad idea, sometimes they help with uncluttering, hiding information that isn't needed by most people but available from the already loaded document on request when needed.
An example would be to provide contact details on a page, usually visitors don't care, but when they do, that information is easy to find.
Another example would be an event log with the summaries always being visible and the details being available inline on request. sourcehut and YunoHost use this pattern.
Note: Accordions usually are multiple collapsible elements grouped together and only allow one open at a time. I'm not going into that functionality here.
The HTML way of building an accordion …
This pattern is so useful that HTML has elements for exactly that usecase, the
Note: Apparently the details element and accessibility is it's own can of worms, too. As the article mentions: You decide.
What people are doing instead …
You may have been surprised by the fact that there is an HTML element for exactly this purpose because you have never seen one in the wild.
There are probably three reasons people don't use it:
- They simply don't know that it exists
- They found out, tried to animate it and failed to do so
- They use a library which fell victim to one of the previous reasons.
The result being that they give up and write their own accordion from scratch, button
Why is the details element seemingly hard to animate?
Animating a details tag is simple, one can use the
[open=""] CSS selector to find out whether the details element is currently open, put some keyframes and an animation (more details later) on it and it opens with a smooth animation and closing is … not animated at all.
No your CSS isn't at fault here.
How the details element works.
The details element is a bit of a unicorn in that it manipulates the page tree. When the content gets shown elements are added and when it closes the elements get removed from the page instantly, and while that is great as it saves resources PR is not happy because they want an animation.
My personal guess is that this is where most developers who know about the details element give up.
Animating it Anyway
Note: Animating slightly degrades accessibility (which is probably still better than with your completely custom accordion) of the details element.
You can find the final result over on Codeberg.
Let's start with the content, I'll assume you have a plain details and summary tag filled with your content and we want to leave it as it is.
Your DNS records for example.org are not configured
Add an A record pointing to 22.214.171.124 .
Add an AAAA record pointin to 2606:2800:220:1:248:1893:25c8:1946
It is important to set both, otherwise peopley may be unable to access your server.
In case you want to follow along but only have a desktop browser: open a new tab, navigate to
about:blank and open up the developer tools. Depending on the browser you now have a pretty good IDE with live preview.
The Grand Opening
Before we worry about closing the element, lets worry about it opening.
The simplest way here is to simply animate the
max-height property from the current height of the element (
--details-current-height) to one screen height (
100vh), filling only back in time in case the details element is taller than one screen height (i.e. on a smaller device), in that case it will simply snap to full height off screen and is fully viewable.
Because we currently have no idea what the current height of the details element is, falling back to an assumed
1em as the height of our summary element works pretty well. (You may have to adjust this based on extra padding and margin you are applying)
With that the opening part is taken care of.
Fading out of Existence
Because the details element immediately closes and there is no way to animate that we have to put a delay between the actual interaction with the element and the
Because we want to hook on the interaction of a sighted user with the summary element, we use an
onclick handler here and assume a
The following function simply adds the
onclick handler to every summary element on the page (if you only want to target specific elements … I'll leave that as an exercise).
To run this initalizer you can use any method you like, but since in my experience
document.onload is a bit wonky when testing inline code on small documents here is a way to defer it until the first interaction.
With that set up we can now focus on the click handler, which has to do two things:
- add a style-class that will trigger the animation
- set a timer to actually close the element after the animation.
100vh to the height of the summary element defaulting to
Again we'll worry about actually setting those variables to something useful later.
Now you should have a not pretty but animated closing details element.
Improving the Animation
Now that we have a proof of concept, lets make it usable.
The issues we currently have are:
- Handle an interaction while the animation is playing correctly.
- Accessibility impact is larger than it could be.
- The heights used are still placeholders.
- The animation speeds don't match up.
Handling an interaction while the animation is playing
Currently when one interacts while the animation is playing the behaviours is that the element keeps closing, which is not great.
To fix it we have to add some additional checks to our handler function to test if the
closing class is present on an open element. If that's the case we just remove the class gain instead of launching a new timeout and on the timeout part we just discard the timeout if the closing class isn't present.
Lowering the impact on accessibility
There are two problems I'm trying to solve with this one:
- When only relying on something that uses the accessibility interface of the browser (i.e. screenreader only) there is an inexplicable delay between interaction and the element being announced as collapsed, very confusing and/or annoying.
- When navigating by keyboard, no matter what the output device is, the content inside the details element can still be focused while the animation is playing, again potentially confusing and/or annoying.
Based on this we make a simple assumption: If you are not using a pointing device, you are probably not in the group that greatly benefits from an animation.
Note: Of course the "uses a pointer" is a heuristic that will be sometimes be wrong.
To achieve this one can test for the
offsetY values of the event to find out weather the event came from a pointer or not, both are 0 they probably didn't come from a pointer and we tell the browser to do what it would do without us interfering.
To stop the opening animation from playing when we won't deliver a corresponding closing animation we add an
animated class as an additional requirement in our two animation CSS rules.
This way we can make PR and our bosses happy while mostly staying out of the way of people who appreciate clean websites more than fancy animations.
Deglitching the animation
Now that we have taken care of accessibility issues, we can put the CSS variables introduced earlier to use to make the animation responsive to the current state to avoid some glitches.
This means populating the
--details-current-height properties directly on the details elements.
Getting the timings to match
After that is fixed the last open issue is, that the speed of the animation opening and the speed of the animation closing don't match.
With the opening speed currently being one screen height minus summary per second it is pretty easy to get the closing animation to match that as when the accordion is open we can just ask the browser how tall the details and summary elements are, how tall the screen is and calculate the animation length from that. Since we already know the heights of both relevant elements we'll use the variables from earlier.
To make the actual state match the timeout previously set to
500 should now be
close_animation_duration*1000+10 the extra 10ms is to allow the browser a bit of breathing room so that we don't cut the animation off before it has finished.
The final result
details_click_handler function now should look like the following.
And our final CSS:
- Added note about accordions usually having a grouping functionality.
- Added link to a [great article by Scott O'Hara explaining the problems around how details element is implemented in browsers].
- Added explanation why I'm disabling the animation for non-pointer inputs here.