Animated Accordion
This is the single most common animation failure in AI-generated interfaces: a panel that opens by transitioning height from zero to auto. That transition does not run — auto is not an interpolatable length — so the panel either snaps open or, worse, the model measures the content in JavaScript and the layout jumps on resize. The grid technique below animates the real height without measuring anything.
Live demo
Because auto is a keyword, not a length. The browser resolves it at layout time, so there is no numeric start or end value for the transition to interpolate between. Every workaround that measures scrollHeight in JavaScript inherits the same problem later: the measurement goes stale when the viewport changes or the content reflows.
A grid row sized 0fr collapses to zero; the same row sized 1fr takes exactly the content height. Both are flexible lengths, so the browser can interpolate between them. The panel is a grid with one row, and the inner element carries overflow: hidden so the content is clipped while the row is short.
For a visual accordion, no — details and summary give you open and closed for free, and CSS can style the marker. What they do not give you is the height transition, because the browser toggles display rather than a height. The version here is a button plus a panel, with one line of JavaScript toggling a class and the ARIA attribute.
The prompt
Build an accordion with a height transition that animates smoothly.
Markup:
- Each item is a container holding a button and a panel.
- The button carries aria-expanded ("true" or "false") and aria-controls pointing at the panel id. The panel has role="region" and aria-labelledby pointing at the button id.
- The panel contains exactly one child element that holds the content. That inner element is what gets clipped.
CSS:
- The panel is display: grid with grid-template-rows: 0fr and overflow: hidden applied to the INNER child, not the panel.
- The open state sets grid-template-rows: 1fr.
- Transition grid-template-rows over 220 to 280ms with an ease-out curve. Add a small padding transition if the content has padding, because padding is not part of the grid track.
- Do NOT transition height, max-height, or use scrollHeight measurements. height: auto cannot interpolate, and max-height with a guessed value either clips long content or finishes early on short content.
- Animate the open/close icon with a transform, not by swapping text.
Behaviour:
- Clicking an open item closes it; clicking another opens that one. Decide explicitly whether the accordion is single-open or multi-open and implement only that.
- Keyboard: Enter and Space activate the button natively; Tab moves between headers; do not add custom key handlers beyond that.
- Under prefers-reduced-motion, remove the transition entirely so panels open instantly.
Constraints:
- Content inside a closed panel must not be reachable by Tab. Use inert or visibility on the inner element, not display: none on the panel (which would kill the transition).
- Long content must scroll the page rather than the panel; the panel grows to its natural height when open.
What actually makes it work
0frand1frare both flexible lengths. That is the entire trick. A grid row at0frhas zero height and a row at1frresolves to the content's height, and because both are lengths the browser can interpolate between them frame by frame.overflow: hiddengoes on the inner child. Putting it on the panel instead breaks the transition in some browsers, because the row can no longer size to its content. The inner element is the clip.- Padding is not inside the track. If the content has padding, the row animates but the padding appears instantly, which reads as a jump at both ends. Transition padding alongside, or move the padding to the inner element's children.
aria-expandedis the state. The class is for styling; the attribute is what assistive technology reads. Both must change in the same function, or a screen reader announces an open panel that looks closed.- Closed content must be unreachable. A collapsed row is still in the accessibility tree.
inerton the inner element keeps Tab out without killing the animation.
Compatibility and performance
| Aspect | Detail |
|---|---|
| grid-template-rows transition | Chrome 107+, Firefox 66+, Safari 16+. Older Safari needs a max-height fallback. |
| Cost | Layout on every frame while animating. Acceptable for one panel at a time; do not open twelve at once. |
| Alternative | interpolate-size: allow-keywords makes height: auto animatable in Chromium 129+, but it is not in Safari or Firefox yet. |
| Nested panels | Works, but the inner panel's 1fr resolves inside the outer one's track; test with long content before shipping. |
One practical note: the animation is layout-driven, unlike the transform-based effects elsewhere in this library. That is fine for a single panel with a few hundred pixels of content, and it is the reason an accordion should never be the element you open on every scroll event.
When not to use it
Don't hide essential content behind it. Anything a visitor must read to complete a task should be visible. An accordion is for secondary detail, specifications and FAQs.
Don't put more than one long panel in the same view. Opening a 2,000px panel pushes everything below it out of sight, and the visitor loses the header they just clicked.
Don't use it for navigation. A menu that expands in place is not a disclosure; it needs different focus management and different semantics from a FAQ accordion.