Transitioning to Height Auto (Two Ways)

Update 3 May 2023: It looks like the origin of this trick in grid traces back to this post from Nelson Menezes in 2021.

One of my longstanding annoyances with CSS has been the inability to transition to height auto. It’s an incredibly common use-case that’s needed for dropdowns and accordions and all sorts of other UI patterns. Unfortunately, this just doesn’t work:

/* Doesn't work */
.dropdown {
  height: 0;
  transition: height 0.5s ease-out;
}

.dropdown.is-open {
  height: auto;
}

It’ll open and close just fine, but it won’t animate. The normal course of action here has been to pull out JavaScript and set the height explicitly based on the element’s scrollHeight.

However, I have come across a two different ways to solve the problem without the help of JavaScript. One approach transitions an element inside a flex item; the other approach transitions an element as a grid item. Each requires adding one or two extra elements to the page to pull it off, but in many cases, that’s still better than the alternative.

With flexbox

I found this solution from Joakim Chiem in a comment buried in the W3C GitHub issue. (The comment is two and a half years old. I am so sorry for not writing about it before now!)

For this approach, wrap the element you want to transition in two divs: the outer will be a flexbox and the inner one will be a flex item. With these in place, you can transition max-height from 0 to 100%. This leverages a quirk of how height/max-height work inside flexbox.

.wrapper {
  display: flex;
}

.inner {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.5s ease-out;
}

.wrapper.is-open .inner {
  max-height: 100%;
}

Just be sure there's an extra div between the wrapper and the inner element:

<div class="wrapper">
  <div>
    <div class="inner">Expandable content</div>
  </div>
</div>

And here's what it looks like in action:

This demo won’t show the transition because you have reduced motion settings enabled.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mi enim, venenatis non facilisis sed, finibus in enim. Sed auctor enim nisl, sit amet feugiat risus blandit vitae.

Integer dapibus tortor lorem, quis interdum erat maximus eu. Nam varius, nisl ac pharetra convallis, neque ex blandit quam, sit amet condimentum lectus elit vel ligula. Donec commodo porta elit quis facilisis. Sed non purus a massa convallis ultricies. Donec malesuada sollicitudin dui, vel lobortis arcu rutrum ut. Curabitur lobortis ac tellus non tempor. Donec nisl risus, cursus vel odio quis, convallis euismod ante.

Nulla lectus diam, sagittis id urna in, tincidunt facilisis sapien. Sed ante turpis, porttitor a diam at, auctor lobortis sapien. Cras ornare dolor sed arcu laoreet volutpat ut tristique ipsum.

The downside to this approach is that, while the inner element animates smoothly, its container does not; that just snaps into place instantly. But that might be prefereable in some situations, since the browser doesn't need to constantly reflow all the contents of the page beneath it.

With grid

I first saw mention of this technique from Chris Coyier, but he kinda buried the lede amid a longer wish-list of CSS features.

This one is a bit simpler, and probably a little more understandable. Make a CSS grid with a single grid item. All you have to do is transition grid-template-rows from 0fr to 1fr, so the grid item transitions to its auto height:

.wrapper {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.5s ease-out;
}

.wrapper.is-open {
  grid-template-rows: 1fr;
}

.inner {
  overflow: hidden;
}

This does not require the extra div. Just a grid container and its one grid item:

<div class="wrapper">
  <div class="inner">Expandable content</div>
</div>

Surprisingly, it’s that simple:

This demo won’t show the transition because you have reduced motion settings enabled.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mi enim, venenatis non facilisis sed, finibus in enim. Sed auctor enim nisl, sit amet feugiat risus blandit vitae.

Integer dapibus tortor lorem, quis interdum erat maximus eu. Nam varius, nisl ac pharetra convallis, neque ex blandit quam, sit amet condimentum lectus elit vel ligula. Donec commodo porta elit quis facilisis. Sed non purus a massa convallis ultricies. Donec malesuada sollicitudin dui, vel lobortis arcu rutrum ut. Curabitur lobortis ac tellus non tempor. Donec nisl risus, cursus vel odio quis, convallis euismod ante.

Nulla lectus diam, sagittis id urna in, tincidunt facilisis sapien. Sed ante turpis, porttitor a diam at, auctor lobortis sapien. Cras ornare dolor sed arcu laoreet volutpat ut tristique ipsum.

This approach feels a lot cleaner. Only two DOM nodes needed, and all the page content beneath moves fluidly like you probably expect.

The one caveat to both of these is you can’t add any padding to the inner element. If you need padding, you have to add one more extra element inside the collapsed element and put the padding on that.

Loading interactions…

Recent Posts

See all posts