Details component demo

This provides robust animations for <details> elements while trying to avoid affecting the accessibility or semantics of the elements.

This was originally inspired by How to Animate the Details Element on CSS-Tricks but was completely overhauled from the original implementation; unlike the CSS-Tricks implementaton, we do not listen to click or other UI events, instead acting only on the toggle event which is the recommended approach as it's triggered whenever anything adds or removes the open attribute or changes the open DOM property, be it mouse, keyboard, touch, a screen reader, etc. We also do not change the the open attribute or property at any point to accomplish the animation, unlike the CSS-Tricks solution.

While it is possible to create a CSS-only solution, there are multiple limitations with such an approach:

  1. There is no way to prevent the content disappearing instantly before the close animation has started without resorting to moving the content outside of the <details> because browsers seem to provide no way to force the content to be displayed once the the open attribute is removed. No, really - unlike a lot of things, you can't even force the content to be visible via display: block !important; visibility: visible !important; or similar; go and try.
  2. There is currently no CSS-only way to transition from a fixed height to the natural content height; the workaround most solutions use is to animate the max-height which is set to a large value when open so that there's a fixed height to transition to; while this does transition open and closed, the open transition doesn't come to smooth stop but usually ends abruptly due to the transition continuing on past the natural content height.

To implement the requirements above, a JavaScript solution was needed. Animation is done via the Motion One library, which allows for precise start and end heights. That solved part of the problem, but:

  1. We couldn't rely on the content itself to measure the height of, as it would take up zero height half the time, i.e. when the open attribute was present on the <details>.
  2. Even with an accurate height being always known, the existing content would instantly disappear before the close animation had begun.

The one place in the <details> that the browser doesn't automagically hide? The <summary> element. We leave the existing content where it is but clone it to the <summary> element; care is taken to hide the clone from the accessibility tree and to make it completely uninteractable via the inert attribute in browsers that support it and falling back to ally.maintain.disabled in ones that don't. This clone is what we use to always have an up to date and accurate measurement of the content height, and doubles as what you see sliding in and out during the open and close animations. The clone is also kept up to date with the real content so any DOM manipulations after we attach are taken into account.

They can start open

Just add the open attribute.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

This posed additional challenges as parent or ancestor <details> elements now needed to account for child or descendent <details> being open; the original implementation relied on setting an inline height and updating that whenever the content size changed, but this turned out to be a nightmare to keep synced and also not lag behind and so have visible content overflow for a few frames. The solution involves only ever having a fixed height during the open and close animations, rebuilding the cloned content before every animation, and the last piece of the puzzle: an animated <details> will trigger an event when its cloned content is rebuilt, which bubbles up the DOM tree, allowing any parent or ancestor <details> to rebuild its copy of the cloned content, all without having to know the specifics of its children or descendents.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

In other words, this is how it behaves if the JavaScript fails to load or attach: it falls back to the browser default which is not as smooth, but you can still access the content within.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

This is how it looks with your browser's default styles. Pretty boring, right?

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

In case you were wondering how that would look.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.