Below is a simplified (and exaggerated) example of an accordion that uses grid to transition the height of the content.
The transition works, but behaves weirdly in Chrome: the container (.accordion
) starts growing quicker than the content, and it takes the content a while to catch up in height, so during the transition there's a gap at the bottom of the container (see screenshot).
In Firefox, everything works as expected.
Using a different transition function (e.g. linear
) affects only the transition of the container; the discrepancy remains.
Is this a bug in Chrome, and if so, is there a workaround?
.accordion {
display: grid;
grid-template-rows: minmax(0, 0fr) min-content;
transition: grid-template-rows 3000ms;
border: 1px solid;
padding: 1rem;
}
.accordion.open {
grid-template-rows: minmax(0, 1fr) min-content;
}
.content {
overflow: hidden;
}
<div class="accordion">
<div class="content">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores aut deserunt eaque incidunt labore laudantium minus nam omnis placeat quas quo, reprehenderit rerum soluta veritatis! Accusamus aliquam atque autem consequatur cumque, deserunt doloremque dolorum eaque eligendi esse est eum excepturi fugit hic impedit incidunt minus odit quae, ut voluptas.
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores aut deserunt eaque incidunt labore laudantium minus nam omnis placeat quas quo, reprehenderit rerum soluta veritatis! Accusamus aliquam atque autem consequatur cumque, deserunt doloremque dolorum eaque eligendi esse est eum excepturi fugit hic impedit incidunt minus odit quae, ut voluptas.
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores aut deserunt eaque incidunt labore laudantium minus nam omnis placeat quas quo, reprehenderit rerum soluta veritatis! Accusamus aliquam atque autem consequatur cumque, deserunt doloremque dolorum eaque eligendi esse est eum excepturi fugit hic impedit incidunt minus odit quae, ut voluptas.
</div>
<button onclick="this.parentElement.classList.toggle('open');">toggle</button>
</div>
I would want to avoid animating grid-template-rows
when using values of less then 1fr
and having the grid-height depend on the content height.
Wrapping with a new div and extracting the button from the grid is one solution that should work.
.accordion {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 3000ms;
overflow: hidden;
align-items: flex-start;
/* stretch is causing strange behavior, flex-start forces the element to have predictable height*/
}
.content {
overflow: hidden;
}
.accordion.open {
grid-template-rows: 1fr;
}
.wrapper {
border: 1px solid;
padding: 1rem;
}
button {
width: 100%
}
<div class="wrapper">
<div class="accordion">
<div class="content">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores aut deserunt eaque incidunt labore laudantium minus nam omnis placeat quas quo, reprehenderit rerum soluta veritatis! Accusamus aliquam atque autem consequatur cumque, deserunt
doloremque dolorum eaque eligendi esse est eum excepturi fugit hic impedit incidunt minus odit quae, ut voluptas. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores aut deserunt eaque incidunt labore laudantium minus nam
omnis placeat quas quo, reprehenderit rerum soluta veritatis! Accusamus aliquam atque autem consequatur cumque, deserunt doloremque dolorum eaque eligendi esse est eum excepturi fugit hic impedit incidunt minus odit quae, ut voluptas. Lorem ipsum
dolor sit amet, consectetur adipisicing elit. Aperiam asperiores aut deserunt eaque incidunt labore laudantium minus nam omnis placeat quas quo, reprehenderit rerum soluta veritatis! Accusamus aliquam atque autem consequatur cumque, deserunt doloremque
dolorum eaque eligendi esse est eum excepturi fugit hic impedit incidunt minus odit quae, ut voluptas.
</div>
</div>
<button onclick="this.parentElement.children[0].classList.toggle('open');">toggle</button>
</div>
Other solutions might also fit your need. It seems you're implementing a work-around for animating to height: auto
. There's other work-arounds with different trade-offs.
IE height: calc-size(auto);
landed in chrome-canary which should make a transition from height: 0
to height: auto
easier, But adoption for all borwsers is surely going to take some time.
Fr units: take a fraction of the available space.
While transitioning from 0fr
to 1fr
. we'll be at 0.5fr
at the halfway mark of the animation.
At that point, our grid-rows will be 0.5fr
+ min-content
for the 2 rows.
The total height of the grid: content height + button height = 100%
Now we've come the crux (and the discrepancy in browser implementation).
What is 0.5fr, or 50% of the available space? The total space is dependent on the child-size, and the child-size depends on total space.
100px
), and have that be equal to the total size = 100px
100px
), calculate the total (100px
) and then scale the children again to the 'available space' (0.5fr
, 50px
)Who's correct?
I don't know, but the firefox one makes more sense to me.
(Arguably) Chrome! The specification mentions this at the definition of 'flex-factor':
Note: If the sum of the flex factors is less than 1, they’ll take up only a corresponding fraction of the leftover space, rather than expanding to fill the entire thing.
As a side note; fr
units and minmax
are mostly useful when there's any other element that is taking up space. If all units are defined as fr
, you might as well use percentages.
for example
1fr 1fr
= 50% 50%
.0.5fr 1.5fr
= 25% 75%
minmax(200px, 1fr), 1fr
= when > 400px 50% 50%
, when < 400 200px remainder
, this can't be achieved with just percentages.minmax(0, 1fr), 1fr
= no mater what size 50% 50%
Comparison between chrome and firefox:
.accordion {
width: 150px;
display: grid;
grid-template-rows: .5fr min-content;
border: 1px solid;
padding: 1rem;
overflow: hidden;
}
.content {
overflow: hidden;
}
.align-start {
align-items: flex-start;
}
.example {
margin: 50px;
padding: 10px;
background: linen;
<div class="example">Example: .5fr
<div class="accordion">
<div class="content">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores aut deserunt eaque incidunt labore laudantium minus nam omnis placeat quas quo, reprehenderit rerum soluta veritatis! Accusamus aliquam atque autem consequatur cumque, deserunt
doloremque dolorum eaque eligendi esse est eum excepturi fugit hic impedit incidunt minus odit quae, ut voluptas.
</div>
<button onclick="this.parentElement.classList.toggle('open');">toggle</button>
</div>
</div>
<div class="example">
Example: .5fr with 'align-items: flex-start'
<div class="accordion align-start">
<div class="content">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores aut deserunt eaque incidunt labore laudantium minus nam omnis placeat quas quo, reprehenderit rerum soluta veritatis! Accusamus aliquam atque autem consequatur cumque, deserunt
doloremque dolorum eaque eligendi esse est eum excepturi fugit hic impedit incidunt minus odit quae, ut voluptas.
</div>
<button onclick="this.parentElement.classList.toggle('open');">toggle</button>
</div>
</div>
<div class="example">
Example: .5fr with 'align-items: flex-start' moved button out of grid
<div class="accordion align-start">
<div class="content">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores aut deserunt eaque incidunt labore laudantium minus nam omnis placeat quas quo, reprehenderit rerum soluta veritatis! Accusamus aliquam atque autem consequatur cumque, deserunt
doloremque dolorum eaque eligendi esse est eum excepturi fugit hic impedit incidunt minus odit quae, ut voluptas.
</div>
</div>
<button onclick="this.parentElement.classList.toggle('open');">toggle</button>
</div>