Search code examples
htmlcsscss-grid

CSS grid - Creating a responsive generic dashboard containing cards of various widths


I'm trying to create a very generic dashboard. I have the Bootstrap breakpoints:

  • 576px
  • 768px
  • 992px
  • 1200px
  • 1400px

I have 3 different types of cards:

  • 1 column wide
  • 2 columns wide
  • 4 columns wide

On the left and on the right side we have 20px padding.

  • the 2 column layout appears at 768px => 2 * 354px + 3 * 20px = 768px
  • the 3 column layout appears at 992px => 3 * 304px + 4 * 20px = 992px
  • the 4 column layout appears at 1200px => 4 * 275px + 5 * 20px = 1200px

The 4 column wide card can only be 4 columns wide when we actually have 4 columns. If we force the 4 column width on small screens it breaks the layout. On small screens we only have 1 column. On a tablet we might have only 2 columns. In those scenarios the 4 column card should be as large as possible but never larger than 4 columns.

The underlying question is: How can I specify the width for my cards and at the same time keep the responsive behavior? The challenge is not to specify fixed columns, e.g. grid-column: 1 / 3. I'm getting the cards from the backend as a flat array. Each card then tells me if it wants to be 1, 2, or 4 columns wide. The cards do not have a certain order and the order might also change.

I also have a demo at StackBlitz - https://stackblitz.com/edit/js-ryxler?file=style.css

My questions are:

  1. Is this the best way to solve my problem?
  2. Is there an option to do this without media queries?

body,
html {
  margin: 0;
}


/* 

  We're using the bootstrap breakpoints:
    - 576px
    - 768px
    - 992px
    - 1200px
    - 1400px

  We have 3 different types of cards:
    - 1 column wide
    - 2 columns wide
    - 4 columns wide

  On the left and on the right side we have 20px padding.

  The 2 column layout appears at 768px => 2*354px + 3*20px = 768px

  The 3 column layout appears at 992px => 3*304px + 4*20px = 992px

  The 4 column layout appears at 1200px => 4*275px + 5*20px = 1200px

  ! Problem:

  The 4 column wide card can only be 4 columns wide when we actually have 4 columns.
  If we force the 4 column width on small screens it breaks the layout.
  On small screens we only have 1 column.
  On a tablet we might have only 2 columns.
  In those scenarios the 4 column card should be as large as possible but never larger than 4 columns.

*/

.wrapper {
  padding: 20px;
  display: grid;
  grid-auto-flow: dense;
  grid-template-columns: repeat(auto-fill, minmax(354px, 1fr));
  grid-auto-rows: 250px;
  gap: 20px;
}

.blue {
  background-color: lightblue;
}

.coral {
  background-color: lightcoral;
  grid-row: span 2;
}

.salmon {
  background-color: lightsalmon;
}

.grid-column-2 {
  grid-column: span 1;
}

.grid-column-4 {
  grid-column: span 1;
}


/* 2 columns */

@media (min-width: 768px) {
  .grid-column-2 {
    grid-column: span 2;
  }
  .grid-column-4 {
    grid-column: span 2;
  }
}


/* 3 columns */

@media (min-width: 992px) {
  .wrapper {
    grid-template-columns: repeat(auto-fill, minmax(304px, 1fr));
  }
  .grid-column-4 {
    grid-column: span 3;
  }
}


/* 4 columns */

@media (min-width: 1200px) {
  .wrapper {
    grid-template-columns: repeat(auto-fill, minmax(275px, 1fr));
  }
  .grid-column-4 {
    grid-column: span 4;
  }
}
<div class="wrapper">
  <div class="blue"></div>
  <div class="blue"></div>
  <div class="salmon grid-column-2"></div>
  <div class="blue"></div>
  <div class="blue"></div>
  <div class="coral grid-column-4"></div>
  <div class="salmon grid-column-2"></div>
  <div class="blue"></div>
  <div class="blue"></div>
  <div class="blue"></div>
  <div class="coral grid-column-4"></div>
  <div class="blue"></div>
  <div class="blue"></div>
  <div class="blue"></div>
  <div class="blue"></div>
  <div class="blue"></div>
  <div class="salmon grid-column-2"></div>
</div>

Thank you!


Solution

  • You can't specify exact width on exact breakpoint without media query but you can remove some CSS property

    For exemple

    // This will make it take all the columns available
    .grid-column-4 {
      grid-column: 1 / -1;
    }
    

    body,
    html {
      margin: 0;
    }
    
    .wrapper {
      padding: 20px;
      display: grid;
      grid-auto-flow: dense;
      grid-template-columns: repeat(auto-fill, minmax(354px, 1fr));
      grid-auto-rows: 250px;
      grid-gap: 20px;
    }
    
    .blue {
      background-color: lightblue;
    }
    
    .coral {
      background-color: lightcoral;
    }
    
    .salmon {
      background-color: lightsalmon;
    }
    
    .grid-column-4 {
      grid-column: 1/-1;
      // I saw that you were initialising this value on every breakpoint
      // this will make it take all the columns available
    }
    
    
    /* 2 columns */
    
    @media (min-width: 768px) {
      .grid-column-2 {
        grid-column: span 2;
      }
    }
    
    
    /* 3 columns */
    
    @media (min-width: 992px) {
      .wrapper {
        grid-template-columns: repeat(auto-fill, minmax(304px, 1fr));
      }
    }
    
    
    /* 4 columns */
    
    @media (min-width: 1200px) {
      .wrapper {
        grid-template-columns: repeat(auto-fill, minmax(275px, 1fr));
      }
      .grid-column-4 {
        grid-column: span 4;
      }
    }
    <div class="wrapper">
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="salmon grid-column-2"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="coral grid-column-4"></div>
      <div class="salmon grid-column-2"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="coral grid-column-4"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="salmon grid-column-2"></div>
    </div>


    You could also use CSS variables to make it more modular

    :root {
      --size: 354px;
    }
    
    body,
    html {
      margin: 0;
    }
    
    .wrapper {
      padding: 20px;
      display: grid;
      grid-auto-flow: dense;
      grid-template-columns: repeat(auto-fill, minmax(var(--size), 1fr));
      grid-auto-rows: 250px;
      grid-gap: 20px;
    }
    
    .blue {
      background-color: lightblue;
    }
    
    .coral {
      background-color: lightcoral;
    }
    
    .salmon {
      background-color: lightsalmon;
    }
    
    .grid-column-4 {
      grid-column: 1/-1;
      // I saw that you were initialising this value on every breakpoint
      // this will make it take all the columns available
    }
    
    
    /* 2 columns */
    
    @media (min-width: 768px) {
      .grid-column-2 {
        grid-column: span 2;
      }
    }
    
    
    /* 3 columns */
    
    @media (min-width: 992px) {
       :root {
        --size: 304px;
      }
    }
    
    
    /* 4 columns */
    
    @media (min-width: 1200px) {
       :root {
        --size: 275px;
      }
      .grid-column-4 {
        grid-column: span 4;
      }
    }
    <div class="wrapper">
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="salmon grid-column-2"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="coral grid-column-4"></div>
      <div class="salmon grid-column-2"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="coral grid-column-4"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="blue"></div>
      <div class="salmon grid-column-2"></div>
    </div>