How can I animate from 0 to auto? I have a fixed width and want it to transition to the width set by its children. How do I do that? These CSS questions have been asked many times on platforms such as Stackoverflow, and I’ve certainly done some searching for those exact questions. I can’t count the number of max-width hacks or JavaScript solutions I’ve used to transition from 0 to auto. No more! I’m done, and for now, I’m going full progressive enhancement. In this article, I’d like to cover some of the benefits you get from adding the new interpolate-size property in your CSS Reset.
So what’s this all about?
Well, it’s about this little CSS snippet that I will be adding to my CSS reset from now on:
:root {
@supports (interpolate-size: allow-keywords) {
interpolate-size: allow-keywords;
}
}
That is, I will be adding this mostly for new projects. I’ll get back to maintaining some legacy projects later in this article. The thing that happens with this small snippet is that interpolate-size: allow-keywords
sets a flag on a :root
level, allowing you to transition from and to widths and heights that have a keyword value, for example: auto
, max-content
, min-content
, stretch
, etc…
To say it in fancy words: It allows you to animate between an intrinsic-size-keyword and a length-percentage.
At the time of writing this is only available in Chrome, which is why it’s best to add the @supports
feature query.
Take the following demo as an example and hover or focus the buttons:
By setting the interpolate-size
on a root level, I was able to animate the text of the buttons when hovering or focusing on them, by going from a fixed width to an auto width:
button {
width: 4rem;
overflow-x: clip;
transition: width 0.35s ease;
white-space: nowrap;
&:is(:hover, :focus) {
width: auto;
}
}
So this demo is actually not that much work, which is great, that’s the whole point of this demo.
It just gave me the superpower to animate to by using the auto keyword.
You can choose to do this case by case with calc-size()
If you’re like me, you’re also maintaining some projects you initially created some years ago. In those cases, you might not feel “safe” in changing the default behavior, because in some cases things could break or create unwanted animations.
In this case, the calc-size()
function is a great idea. Instead of setting interpolate-size: allow-keywords
on the root level, we could - based on the previous example - do the following:
button {
/* same as before */
&:is(:hover, :focus) {
width: auto;
@supports (width: calc-size(auto, size)) {
width: calc-size(auto, size);
}
}
}
You will still need the @supports
query for now as at the moment this is still only available to Chrome. Do note that this is Chrome first, not Chrome only. This is a CSS feature that has a spec and it will come to other browsers eventually. Which makes it the perfect progressive enhancement. If you open the demo in non-supported browsers it just doesn’t animate, and that’s fine in most cases.
Some great use cases where keyword animation can speed up your development
In this section, I thought it would be nice to show where this “animating to size-keywords” can come in handy as a progressive enhancement.
A fun idea could be to have cards that animate on click, whether that is horizontal or vertical (I just had a bit of fun here), the idea is pretty much the same the whole time. In this demo I wanted an image container to grow based on the max-width
of a card. Of course, there are other ways we could do this, but this makes it so easy to write and manage.
This is the code in nutshell:
:root {
@supports (interpolate-size: allow-keywords) {
interpolate-size: allow-keywords;
}
}
.image {
width: 0;
overflow: clip;
transition: width 1s ease;
}
.card:has(button[aria-expanded="true"]) .image {
width: auto; /* set width auto when expanded */
}
.card {
display: flex;
/* ... */
max-width: clamp(280px, 80vw, 600px);
}
What happens here is that the .image
wrapper fills the space available (a space limited by the max-width
of the card). We set this wrapper initially to zero and clip the overflow. When the button is pressed, we animate this .image
wrapper to have a auto
width that just fills the space. It can really help us to create cool effects, fast.
Another case is where we can toggle a setting for a platform’s navigation to show text or icons only, something I’ve seen a few times before. This is in my opinion the perfect example where a user probably doesn’t care whether something animates or not.
In the following demo, you’ll see just that. I also included an extra idea containing a collapse of a warning notification. Just to show some of those things you might encounter…
The code is pretty much the same as before, so I won’t go into it, but maybe it can help you in the future:
I believe in progressive enhancements, and as this is a simple flag to turn on and off, it’s neat that the behavior of a page can be different between browsers, but stays easily consistent in the browser of choice.
Bonus: The update on the details element in combination with interpolate-size
First, I thought I’d write a separate article on this, but it just fits so nicely in these examples. While I was starting to create demos on this, a great article was released on this: more options for styling <details> by Bramus. There are some really neat examples on this and I suggest you read it, but with eyes on the interpolate-size
feature let me summarise it just a little bit.
In my presentation “The future of UI is open” I talk a bit about exclusive accordions. This gives you the possibility to turn the <details>
element into an accordion by combining them using the name
attribute. Here is a basic example of that:
<details name="my-accordion">
<summary>
This is an example
</summary>
<!-- content -->
</details>
<details name="my-accordion" open>
<summary>
Of an accordion
</summary>
<!-- content -->
</details>
Since then, something new was added and set to be released in Chrome 131. The ability to target the content of a <details>
element instead of just the <summary>
. This can be targetted by a pseudo-element named ::details-content
. When reading about this the first time, I thought the following would be enough to animate this (but there is a caveat):
@supports selector(::details-content) {
::details-content {
height: 0;
transition: padding-block 0.3s, height 0.3s ease,
content-visibility 0.3s ease allow-discrete;
overflow: clip;
}
details[open]::details-content {
height: auto;
}
}
Unfortunately, this was not enough. There is a (new) property that is being set on the ::details-content
pseudo-element: content-visibility. This property controls whether or not an element renders its contents. We will need to transition this property as well, and since this is a keyword, it’s a job for transition-behavior: allow-keywords
.
I’ve written some basics on transition-behavior before, but I really should revisit it as the times when we can benefit from it seem to rise.
This is the updated code, note that we don’t actually need to set the values of content-visibility
as these are handled by the user-agent stylesheet, but we do need to set the ability to transition it:
@supports selector(::details-content) {
::details-content {
height: 0;
transition: padding-block 0.3s, height 0.3s ease,
content-visibility 0.3s ease allow-discrete;
overflow: clip;
}
details[open]::details-content {
height: auto;
}
}
Another thing to remember is that allow-discrete
should come last in the transition
shorthand.
Here is the quick animated accordion I made (for Chrome 131):
I know this might be a bit basic, which is why I suggest you check out the article by Bramus, I love the horizontal cards in it.
But once again, this feature is a complete progressive enhancement as in some browsers that don’t support it, it just won’t animate.
Although the HTML part of it has a full browser support… In case a user would use a browser that doesn’t support the name
attribute for <details>
, the details elements would still work, although not in an accordion manner but as single collapses.
And that’s it, just add it!
No need for big conclusions, I can only encourage you to set this in your CSS reset or reboot. This might be one of my favorite candidates for interop as well. It would be nifty to have this in complete browser support.