Search code examples
csshtml-tablescrollcss-griddashboard

Scrolling Issue with table in CSS Grid Cells


I am relatively new to html/css as I have spent most of my career on desktop apps in WPF. I have started to provide a web version of some of our applications and have been enjoying the learning journey. I have recently run into a layout issue and created a sample of what I am trying to achieve. The sample simulates a dashboard style page with cards aligned in a grid. The top level row of the grid is fixed height, where the second row expands and contracts to fill the available space. Coming from WPF/XAML I have been using the CSS grid for most of my layouts. The problem I am having is that in the cards not in the top row, there are tables that I was hoping would provide scroll bars within the individual “cards”. What is really happening is that when the shrinking the height of the window, the overall window’s scroll bar takes over, not the individual card/table scroll bars I was hoping would work by adding the overflow: auto tag to the card’s body section.

I provided the example below which will explain it in a much better way than what I have so far. Any pointers on what I am doing wrong would be much appreciated.

Codepen Example

html body {
   background-color: #111111;
   margin: 0px;
   width: 100vw;
   height: 100vh;
}

.top-level-container{
   display:grid;
   grid-template-rows: [HEADER] auto [BODY] 1fr;
   height: 100%;
}

.top-level-container .header {
   display:grid;
   grid-row: HEADER;
   grid-template-columns: [LEFT] 1fr [CENTER] 1fr [RIGHT] 1fr;
   height: 125px;
}

.card {
   display:grid;
   background-color: #333333;
   border-radius: 8px;
   grid-template-rows: [CARDHEADER] auto [CARDBODY] 1fr [CARDFOOTER] 10px;
}

.card .card-header {
   grid-row: CARDHEADER;
   font-family: Roboto, Arial, Helvetica, sans-serif;
   font-size: x-small;
   font-weight: 700;
   color: white;
   margin: 8px;
}

.card .card-body {
   grid-row: CARDBODY;
   font-family: Roboto, Arial, Helvetica, sans-serif;
   font-size: xx-small;
   font-weight: 500;
   color: white;
   overflow: auto;
}

.card .card-footer {
   grid-row: CARDFOOTER;
}

.top-level-container .header .header-left {
   grid-column: LEFT;
   margin:10px 5px 5px 10px;
}

.top-level-container .header .header-middle {
   grid-column: CENTER;
   margin:10px 5px 5px 5px;
}
.top-level-container .header .header-right {
   grid-column: RIGHT;
   margin:10px 10px 5px 5px;
}

.top-level-container .content {
   display:grid;
   grid-row: BODY;
   grid-template-columns: [LEFT] 1fr [RIGHT] 1fr;
}
.top-level-container .content .content-left {
   display: grid;
   grid-template-rows: [TOP] 1fr [BOTTOM] 1fr;
   grid-column: LEFT;
   margin: 5px 5px 10px 10px;
}

.top-level-container .content .content-left .content-left-top {
   grid-row: [TOP];
   margin-bottom: 5px;
}

.top-level-container .content .content-left .content-left-bottom {
   grid-row: [BOTTOM];
   margin-top: 5px;
}

.top-level-container .content .content-right {
   grid-column: RIGHT;
   margin: 5px 10px 10px 5px;
}

td {
   padding: 4px 10px 4px 10px;
}

tr {
   background-color: #111111;
}
<div class="top-level-container">
  <div class="header">
     <div class="card header-left">
        <div class="card-header">FIXED HEIGHT</div>
     </div>
     <div class="card header-middle">
        <div class="card-header">FIXED HEIGHT</div>
     </div>
     <div class="card header-right">
        <div class="card-header">FIXED HEIGHT</div>
     </div>
  </div>
  <div class="content">
     <div class="content-left">
        <div class="card content-left-top">
           <div class="card-header">SCROLLABLE BODY</div>
           <div class="card-body">
              <table>
                 <tr>
                    <td>00:00:00</td>
                    <td>ComponentX</td>
                    <td>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td>
                 </tr>
                 <tr>
                    <td>00:00:21</td>
                    <td>ComponentY</td>
                    <td>In quis purus a odio facilisis egestas ut in magna.</td>
                 </tr>
                 <tr>
                    <td>00:01:12</td>
                    <td>ComponentZ</td>
                    <td>In ultricies magna et sollicitudin condimentum.</td>
                 </tr>
                 </table>
           </div>
        </div>
        <div class="card content-left-bottom">
           <div class="card-header">SCROLLABLE BODY</div>
           <div class="card-body">
              <table>
                 <tr>
                    <td>00:00:00</td>
                    <td>ComponentX</td>
                    <td>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td>
                 </tr>
                 <tr>
                    <td>00:00:21</td>
                    <td>ComponentY</td>
                    <td>In quis purus a odio facilisis egestas ut in magna.</td>
                 </tr>
                 <tr>
                    <td>00:01:12</td>
                    <td>ComponentZ</td>
                    <td>In ultricies magna et sollicitudin condimentum.</td>
                 </tr>
                 </table>
           </div>
        </div>
     </div>
     <div class="card content-right">
        <div class="card-header">SCROLLABLE BODY</div>
        <div class="card-body">
           <table>
              <tr>
                 <td>00:00:00</td>
                 <td>ComponentX</td>
                 <td>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td>
              </tr>
              <tr>
                 <td>00:00:21</td>
                 <td>ComponentY</td>
                 <td>In quis purus a odio facilisis egestas ut in magna.</td>
              </tr>
              <tr>
                 <td>00:01:12</td>
                 <td>ComponentZ</td>
                 <td>In ultricies magna et sollicitudin condimentum.</td>
              </tr>
              <tr>
                 <td>00:04:02</td>
                 <td>ComponentA</td>
                 <td>Etiam bibendum dui nec velit sagittis, sed gravida elit commodo.</td>
              </tr>
              <tr>
                 <td>00:00:00</td>
                 <td>ComponentB</td>
                 <td>Quisque facilisis odio eu diam malesuada euismod.</td>
              </tr>
              <tr>
                 <td>00:00:21</td>
                 <td>ComponentC</td>
                 <td>Sed tincidunt magna at nulla suscipit, in rhoncus sem ultrices.</td>
              </tr>
              <tr>
                 <td>00:01:12</td>
                 <td>ComponentD</td>
                 <td>Sed et mauris in mauris dapibus faucibus nec quis neque.</td>
              </tr>
              <tr>
                 <td>00:04:02</td>
                 <td>ComponentE</td>
                 <td>Phasellus vulputate turpis in felis suscipit, quis tempor elit molestie.</td>
              </tr>
           </table>
        </div>
     </div>
  </div>
</div>


Solution

  • This is definitely doable, but avoid creating too many nested elements in your HTML. I would suggest that all your cards should be direct children of the top level container. Then you’ll only need two grids: one for the overall page layout (use 6 equal columns and three rows), then one at card level to manage the contents of each card.

    When you run this snippet, make sure to use the full page link.

    const t = `<tr>
       <td>00:00:00</td>
       <td>ComponentX</td>
       <td>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td>
    </tr>
    <tr>
       <td>00:00:21</td>
       <td>ComponentY</td>
       <td>In quis purus a odio facilisis egestas ut in magna.</td>
    </tr>
    <tr>
       <td>00:01:12</td>
       <td>ComponentZ</td>
       <td>In ultricies magna et sollicitudin condimentum.</td>
    </tr>
    <tr>
       <td>00:04:02</td>
       <td>ComponentA</td>
       <td>Etiam bibendum dui nec velit sagittis, sed gravida elit commodo.</td>
    </tr>
    <tr>
       <td>00:00:00</td>
       <td>ComponentB</td>
       <td>Quisque facilisis odio eu diam malesuada euismod.</td>
    </tr>
    <tr>
       <td>00:00:21</td>
       <td>ComponentC</td>
       <td>Sed tincidunt magna at nulla suscipit, in rhoncus sem ultrices.</td>
    </tr>
    <tr>
       <td>00:01:12</td>
       <td>ComponentD</td>
       <td>Sed et mauris in mauris dapibus faucibus nec quis neque.</td>
    </tr>
    <tr>
       <td>00:04:02</td>
       <td>ComponentE</td>
       <td>Phasellus vulputate turpis in felis suscipit, quis tempor elit molestie.</td>
    </tr>`
    document.querySelectorAll('.card-body').forEach(d => {
      d.innerHTML = '<table>' + t + t + t + t + t + '</table>'
    })
    html, body, .top-level-container {  /* these three elements need to be 100% height */
      height: 100%;
    }
    
    body {
      background-color: #111111;
      margin: 0px;
      font-family: sans-serif;
    }
    
    .top-level-container {
      display: grid;
      grid-template-columns: repeat(6, minmax(0, 1fr)); /* six equal-width columns */
      grid-template-rows: 80px repeat(2, minmax(0, 1fr)); /* first row fixed height, followed by two equal-height rows */
      padding: 8px;
      gap: 8px;
      box-sizing: border-box;
    }
    
    .card {
      background-color: #333333;
      border-radius: 8px;
      color: white;
      display: grid;
      grid-template-rows: auto 1fr auto; /* auto for header and footer */
      box-sizing: border-box;
      overflow: hidden;
    }
    
    .card-header, .card-footer {
      font-size: x-small;
      font-weight: 700;
      text-transform: uppercase;
      padding: 8px;
    }
    
    /* FOR IMPROVED VISIBILITY
    .card-header {
      background: blue;
    }
    .card-footer {
      background: maroon;
    }
    */
    
    .card-body {
      font-size: xx-small;
      font-weight: 500;
      overflow: auto;
    }
    
    .card-footer {
      font-size: x-small;
    }
    
    .header-left {
       grid-column: 1 / span 2;
    }
    .header-middle {
       grid-column: 3 / span 2;
    }
    .header-right {
       grid-column: 5 / span 2;
    }
    .content-left-top {
      grid-column: 1 / span 3;
      grid-row: 2;
    }
    .content-left-bottom {
      grid-column: 1 / span 3;
      grid-row: 3;
    }
    .content-right {
      grid-column: 4 / span 3;
      grid-row: 2 / span 2;
    }
    
    table {
      width: 100%;
    }
    td {
      background-color: #111111;
      vertical-align: top;
      padding: 3px 6px;
    }
    <div class="top-level-container">
      <div class="card header-left">
        <div class="card-header">FIXED HEIGHT</div>
      </div>
      <div class="card header-middle">
        <div class="card-header">FIXED HEIGHT</div>
      </div>
      <div class="card header-right">
        <div class="card-header">FIXED HEIGHT</div>
      </div>
      <div class="card content-left-top">
        <div class="card-header">SCROLLABLE BODY</div>
        <div class="card-body"></div>
        <div class="card-footer">footer IF DESIRED</div>
      </div>
      <div class="card content-left-bottom">
        <div class="card-header">SCROLLABLE BODY</div>
        <div class="card-body"></div>
      </div>
      <div class="card content-right">
        <div class="card-header">SCROLLABLE BODY</div>
        <div class="card-body"></div>
        <div class="card-footer">footer IF DESIRED</div>
      </div>
    </div>