cssgoogle-chromecss-gridcss-transitionsaccordion

Chrome: grid container changes height at different rate from content


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?

height transition discrepancy in Chrome

.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>


Solution

  • Wrap and extract

    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.

    What is happening

    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.

    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

    Comparison between chrome and firefox: chrome-firefox-comparison

      .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>