skip to Main Content

I have a responsive grid in which each item has its own details section, which is hidden by default. A click on the item adds a class to the details section to show it.

The HTML/CSS looks roughly like this:

<div class="grid">
    <div class="item">Item 1</div>
    <div class="details">Item 1 Details</div>

    <!-- ... -->

    <div class="item">Item n</div>
    <div class="details">Item n Details</div>
</div>
.grid {
    display: grid;
    grid-auto-flow: dense;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.item {
    /* nothing relevant */
}

.details {
    grid-column: 1 / -1;
    max-height: 0;
    padding: 0;
    opacity: 0;
    overflow: hidden;
}

.details.selected {    
    max-height: unset;
    padding: 1rem 2rem;
    opacity: 1;
}

The details span an entire row, and the dense auto-flow allows other items to fill the gaps.

This works well and looks great with enough items.

However, let’s assume a width of 1000px, meaning 5 items can fit next to each other. If there are only 1 or 2 items, there is too much empty space to their right. I would like to center these items, but with this current CSS, the grid fills in empty cells. This seems to be caused by grid-column: 1/ -1.

So, this is how it currently looks with only 2 items:
This is how it currently looks

This is how I would like it to look:
This is how I would like it to look

In both cases, with more than 5 items it would look like this, which is fine. I’d also be fine with the bottom row being centered too, in case there’s a flex solution.
More than 5 items

How can I achieve this behavior?
It doesn’t need to be grid. Can be flex too, or really any other solution, if the dense behavior can be achieved with it.


Somewhat related question, not my focus right now, but would be nice to have:

I would like to add a fade effect to the details, which is why it uses padding, max-height and opacity instead of just setting it to display: none. If one item is selected and I select another item in the same row, both details containers are displayed for the transition duration, which causes weird shifts in the grid layout.

JS solutions would be okay. It doesn’t need to be pure CSS.

2

Answers


  1. I’m not sure to understand your goal…

    This snippet gives you the lest layout provided, let me know if that’s not the good one…

        #mainContainer{
            display:grid;
            width: 840px;
            justify-content: center;
            align-content: center;
            margin-left:auto;
            margin-right:auto;
            padding:10px;
        }
        #container{
            display:grid;
            width:800px;
            gap: 10px;
            grid-template-columns: repeat(5,20%);
            grid-template-rows: repeat(3,50px);
            font-family: sans-serif;
            justify-content: center;
            align-content: center;
        }
        #cell_0_0_1{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 1;
            grid-column-end: 2;
        }
        #cell_0_0_2{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 2;
            grid-column-end: 3;
        }
        #cell_0_0_3{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 3;
            grid-column-end: 4;
        }
        #cell_0_0_4{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 4;
            grid-column-end: 5;
        }
        #cell_0_0_5{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 5;
            grid-column-end: 6;
        }
        #cell_1_0_6{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 1;
            grid-column-end: 6;
        }
        #cell_2_0_1{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 1;
            grid-column-end: 2;
        }
        #cell_2_0_2{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 2;
            grid-column-end: 3;
        }
        #cell_2_0_3{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 3;
            grid-column-end: 4;
        }
        #cell_2_0_4{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 4;
            grid-column-end: 5;
        }
        #cell_2_0_5{
            display:grid;
            justify-content: center;
            align-content: center;
            grid-column-start: 5;
            grid-column-end: 6;
        }
        .greenBorder{
            border:4px solid green;
        }
        .redBorder{
            border:4px solid red;
        }
        .blueBorder{
            border:4px solid blue;
        }
        <div id="mainContainer" class="redBorder">
            <div id="container" >
                <div id="cell_0_0_1" class="greenBorder">20%</div>
                <div id="cell_0_0_2" class="greenBorder">20%</div>
                <div id="cell_0_0_3" class="greenBorder">20%</div>
                <div id="cell_0_0_4" class="greenBorder">20%</div>
                <div id="cell_0_0_5" class="greenBorder">20%</div>
    
                <div id="cell_1_0_6" class="blueBorder">100%</div>
                
                <div id="cell_2_0_1"></div>
                <div id="cell_2_0_2" class="greenBorder">20%</div>
                <div id="cell_2_0_3"></div>
                <div id="cell_2_0_4" class="greenBorder">20%</div>
                <div id="cell_2_0_5"></div>
            </div>
        </div>
    Login or Signup to reply.
  2. How can I achieve this behavior? It doesn’t need to be grid. Can be flex too, or really any other solution

    The image with two items on one row resembles a flex box layout with the following property settings:

    .grid {
        display: flex;
        justify-content: space-evenly;
        flex-wrap: wrap;
    }
    

    Depending on the width of the browser’s viewport, the layout when two elements are wrapped onto a row by themselves will resemble what you’re looking to achieve in the layout:

    enter image description here

    enter image description here

    .grid-flex-layout {
        --details-padding: 0.5rem;
        --top-bottom-padding: 1rem;
        --border-size: 3px;
    }
    
    .grid-flex-layout {
        display: flex;
        justify-content: space-evenly;
        flex-wrap: wrap;
        border: var(--border-size) solid red;
        padding: 1rem;
    }
    
    .item {
        padding: var(--details-padding);
    }
    
    .item-content {
        border: var(--border-size) solid green;
        padding: 1rem;
    }
    <div class="grid-flex-layout">
      <div class="item">
        <div class="item-content">Item 1 parent element</div>
      </div>
      <div class="item">
        <div class="item-content">Item 2 parent element</div>
      </div>
      <div class="item">
        <div class="item-content">Item 3 parent element</div>
      </div>
      <div class="item">
        <div class="item-content">Item 4 parent element</div>
      </div>
      <div class="item">
        <div class="item-content">Item 5 parent element</div>
      </div>
      <div class="item">
        <div class="item-content">Item 6 parent element</div>
      </div>
      <div class="item">
        <div class="item-content">Item 7 parent element</div>
      </div>
      <div class="item">
        <div class="item-content">Item 8 parent element</div>
      </div>
      <div class="item">
        <div class="item-content">Item 9 parent element</div>
      </div>
      <div class="item">
        <div class="item-content">Item 10 parent element</div>
      </div>
    </div>

    To get the expanding/collapsible .details element, wrap each .item element in a wrapper and include within that wrapper the .details element.

    <div class="item-wrapper">
        <div class="item">
            <div class="item-content">Item 6 parent element</div>
        </div>
        <div class="details">
            <div class="details-content">
                <p>
                    Item 2 Details has a lot more content
                    than other elements of similar type.
                </p>
                <p>
                    The content shows the display of the
                    details content div is not a fixed height.
                </p>
            </div>
        </div>
    </div>
    

    The position of the .details element is set absolute. Its placement would be relative to the grid element and not its parent .item-wrapper in order to stretch the .details across the width of the grid.

    .grid-flex-layout {
        position: relative;
        display: flex;
        justify-content: space-evenly;
        flex-wrap: wrap;
        border: var(--border-size) solid red;
        padding: 1rem;
    }
    
    .item-wrapper .details {
        position: absolute;
        top: auto;
        left: 0;
        width: 100%;
        height: 0;
        opacity: 0;
        overflow: hidden;
        display: grid;
        align-items: start;
    }
    

    The .max-height technique referenced below (or the manual setting of .details height) can be applied to expand/collapse the .details element.

    I would like to add a fade effect to the details, which is why it uses padding, max-height and opacity instead of just setting it to display: none. If one item is selected and I select another item in the same row, both details containers are displayed for the transition duration, which causes weird shifts in the grid layout. JS solutions would be okay. It doesn’t need to be pure CSS.

    I’ve used max-height based on this Stack Overflow post to expand/collapse for my projects. It works well as a CSS-only solution. It doesn’t work well, as you’ve stated, in a responsive grid layout, causing "weird shifts".

    To get a smoother expand/collapse transition, the code snippet below manually calculates height of the content within the .details element when the content height is not the same. The max-height CSS-only solution works well for .details content that’s the same value set in a CSS style rule.

    The snippet uses .scrollHeight but like other properties that return an element’s dimension, it will force layout/reflow. This article explains more.

    CSS animation "lets the browser optimize performance and efficiency" versus multiple CSS transitions used in the snippet.

    The solution relies on CSS variables to share values — that affect content height — between JavaScript and CSS style rules. I didn’t optimize the code but provides a minimum fade effect demo.

    document.addEventListener("DOMContentLoaded", init);
    
    let gridEl;
    let compStyles;
    let borderSize = 0,
      detailsPadding = 0,
      padding = 0;
    
    function init() {
      gridEl = document.querySelector(".grid");
    
      gridEl.querySelectorAll(".grid .item")
        .forEach(el => {
          el.addEventListener("click", itemClick);
        });
    
      compStyles = window.getComputedStyle(gridEl);
    
      // Set 'borderSize' variable that represents the
      // size of the border set for the '.details-content'
      // element.
      borderSize = getCssVariable('--border-size');
    
      detailsPadding = getCssVariable('--details-padding');
      padding = getCssVariable('--top-bottom-padding');
    }
    
    function getCssVariable(cssVar) {
      let value = compStyles.getPropertyValue(cssVar);
      if (value.indexOf("rem") > -1) {
        value = convertRemToPixels(value);
      } else if (value.indexOf("px") > -1) {
        value = parseInt(value, 10);
      }
      return value;
    }
    
    function itemClick(e) {
      // The '.details' element for the clicked '.item'
      // target should be the next element.
      const detailsEl = e.currentTarget.nextElementSibling;
    
      if (!detailsEl.classList.contains("details")) {
        console.error("The next element is not a its '.details' sibling.");
        return;
      }
    
      if (detailsEl.classList.contains("selected")) {
        removeSelected(detailsEl);
      } else {
        // Hide current displayed '.details' element
        const selEl = gridEl.querySelector(".details.selected");
        if (selEl) {
          removeSelected(selEl);
        }
        // Show selected '.details' element
        addSelected(detailsEl);
      }
    }
    
    // https://stackoverflow.com/a/42769683
    // 'rem' parameter includes unit string (e.g. "2rem")
    function convertRemToPixels(rem) {
      return parseFloat(rem, 100) * parseFloat(getComputedStyle(document.documentElement).fontSize);
    }
    
    function addSelected(detailsEl) {
      detailsEl.classList.add("selected");
    
      let height = detailsEl.querySelector(".details-content").scrollHeight;
      height = height + padding + borderSize * 2;
    
      const styles = {
        height: height + "px",
        opacity: 1,
        padding: detailsPadding + "px"
      };
      Object.assign(detailsEl.style, styles);
    }
    
    function removeSelected(detailsEl) {
      detailsEl.classList.remove("selected");
    
      const styles = {
        height: 0,
        opacity: 0,
        padding: 0
      };
      Object.assign(detailsEl.style, styles);
    }
    /* Layout */
    
    .grid {
      --details-padding: 0.5rem;
      --top-bottom-padding: 1rem;
      --border-size: 3px;
    }
    
    .grid {
      display: grid;
      grid-auto-flow: dense;
      /* https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit
            "To achieve wrapping, we can use the auto-fit or
            auto-fill keywords. These keywords tell the browser 
            to handle the column sizing and element wrapping for
            us so that the elements will wrap into rows when the
            width is not large enough to fit them in without any overflow."
        */
      grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    }
    
    .item {
      display: grid;
      /*height: 10rem;*/
      /* Fixed height */
      /*height: fit-content;*/
      /* Stretch height to the content within the '.item' element */
      height: auto;
      /* Stretch height to the largest '.item' element in the row */
    }
    
    .item-content {
      height: auto;
    }
    
    .details {
      grid-column: 1 / -1;
      opacity: 0;
      display: grid;
      overflow: hidden;
      /* https://stackoverflow.com/questions/3508605/how-can-i-transition-height-0-to-height-auto-using-css
            https://medium.com/dailyjs/mimicking-bootstraps-collapse-with-vanilla-javascript-b3bb389040e7 */
      /*max-height: 0;*/
      /*transition: height 0.8s cubic-bezier(0, 1, 0, 1), opacity 0.5s;*/
      height: 0;
      transition: height 0.8s, opacity 0.5s, padding 0.3s;
    }
    
    .details.selected {
      /*max-height: 99em;
            transition: max-height 0.8s ease-in-out;*/
      opacity: 1;
    }
    
    .details-content {}
    
    
    /* Styles */
    
    .grid {
      border: var(--border-size) solid red;
      padding: 1rem;
    }
    
    .item {
      cursor: pointer;
    }
    
    .item-content {
      border: var(--border-size) solid green;
      padding: 1rem;
    }
    
    .item,
    .details.spacing {
      padding: var(--details-padding);
    }
    
    .details-content {
      display: grid;
      box-sizing: border-box;
      padding: var(--top-bottom-padding);
      border: var(--border-size) solid blue;
    }
    
    .details-content>p:last-of-type {
      margin-bottom: 0;
    }
    <div class="grid">
      <div class="item">
        <div class="item-content">Item 1</div>
      </div>
      <div class="details">
        <div class="details-content">Item 1 Details</div>
      </div>
      <div class="item">
        <div class="item-content">Item 2<br />(extra content)</div>
      </div>
      <div class="details">
        <div class="details-content">
          <p>
            Item 2 Details has a lot more content than other elements of similar type.
          </p>
          <p>
            The content shows the display of the details content div is not a fixed height.
          </p>
        </div>
      </div>
      <div class="item">
        <div class="item-content">Item 3</div>
      </div>
      <div class="details">
        <div class="details-content">Item 3 Details</div>
      </div>
      <div class="item">
        <div class="item-content">Item 4</div>
      </div>
      <div class="details">
        <div class="details-content">Item 4 Details</div>
      </div>
      <div class="item">
        <div class="item-content">Item 5</div>
      </div>
      <div class="details">
        <div class="details-content">Item 5 Details</div>
      </div>
      <div class="item">
        <div class="item-content">Item 6</div>
      </div>
      <div class="details">
        <div class="details-content">
          <p>Item 6 Details</p>
        </div>
      </div>
      <div class="item">
        <div class="item-content">
          <p>Item 7<br />has more content<br />than other '.item' elements.</p>
        </div>
      </div>
      <div class="details">
        <div class="details-content">
          <p>Item 7 Details is also<br />a little longer than the previous elements.</p>
        </div>
      </div>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search