Search code examples
cssflexboxgrid-layoutcss-grid

Shrink grid items just like flex items in css


Is it possible to shrink grid items just like flex items in css?

Grid items

.container {
  display: grid;
  grid-gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

.child {
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  justify-content: center;
  padding: 5px;
  border: 3px solid #a07;
}
<div class="container">
  <div class="child">
    text
  </div>
  <div class="child">
    text
  </div>
  <div class="child">
    text
  </div>
  <div class="child">
    text
  </div>
  <div class="child">
    text
  </div>
</div>

jsfiddle

Flex items

.container {
  display: flex;
  margin-left: -10px;
  flex-flow: row wrap;
}

.child {
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  justify-content: center;
  padding: 5px;
  border: 3px solid #a07;
  flex: 200px 1 1;
  margin-left: 10px;
  margin-bottom: 10px;
}
<div class="container">
  <div class="child">
    text
  </div>
  <div class="child">
    text
  </div>
  <div class="child">
    text
  </div>
  <div class="child">
    text
  </div>
  <div class="child">
    text
  </div>
</div>

jsfiddle

In a nutshell I can have the following positioning of the elements with the flex above:

enter image description here

While I can not achieve the same behaviour by using a grid layout. Flex layout allows items to shrink on small screen, while grid layout does not allow. At the same time I would like to preserve the behavior that the item will move to the next line with another item only when after such a placement each one of them will be no shorter than a specific size (200px in our case).

I am using grid layout because it allows to preserve the invariant that widths of all the children will be the same. With flex layout the last item will be stretched to the whole line if it will be alone on the line.


Solution

  • New solution

    An optimized version of the initial solution using min()

    .container {
      display: grid;
      grid-gap: 10px;
      grid-template-columns: repeat(auto-fill, minmax(min(200px,100%), 1fr));
    }
    
    .child {
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 5px;
      border: 3px solid #a07;
    }
    
    body {
      margin:0;
    }
    <div class="container">
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
    </div>

    Old solution

    One solution is to specify a max-width to the child element relying on the viewport unit since percentage values are relative to the size of the track defined by the minmax() and cannot be used. This solution isn't generic and you need to adjust the value depending on each situation.

    In you case for example, we can use 100vw since the container is the only element in the body and is taking the whole width:

    .container {
      display: grid;
      grid-gap: 10px;
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    }
    
    .child {
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 5px;
      border: 3px solid #a07;
      max-width:100vw;
      box-sizing:border-box; /* Don't forget this !*/
    }
    
    body {
      margin:0;
    }
    <div class="container">
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
    </div>

    In case there is more element or some padding/margin you need to consider them within the max-width calculation:

    .container {
      display: grid;
      grid-gap: 10px;
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    }
    
    .child {
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 5px;
      border: 3px solid #a07;
      max-width:calc(100vw - 40px); /*we remove the body margin*/
      box-sizing:border-box; 
    }
    
    body {
      margin:0 20px; 
    }
    <div class="container">
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
      <div class="child">
        text
      </div>
    </div>

    It's like we no more have 2 constraints but 3:

    • The grid cell has a minimum size of 200px
    • The grid cell fill the remain space
    • The element inside the grid cell has a maximum size defined relatively to the screen size. (the shrink constraint we were missing)