Table tbody merges with thead on scrollable HTML table

72 Views Asked by At

On a hybrid web page, one section includes a scrollable table of names with some information about them. Ideally, this table would have fixed headings with scrollable rows below. But I cannot seem to get this to work correctly.

The glaring problem here, is that (at least on Chrome latest) the row data is merged with (does not overwrite) the headings so that both values occupy the same space, making either difficult to read. One other problem is the headings move (only by a few pixels, but they do move) as the table is scrolled. While we can live with the latter, it seems unprofessional and not ideal.

I have tried inserting thead ... /thead tbody ... /tbody tags in the appropriate places... the result is that the alignment of colums no longer matches between header and body.

I've found some thirty!! answers to this sort of problem at this question. If I remove the top:0 specification from the th atttributes, the heading scrolls with the body. Same with position:sticky.

If I remove 'overflow:auto' from the tbody, as seems to be suggested in the above post, I lose the ability to scroll and the table grows beyond the desired window.

Some of the "working solutions" in that post include JavaScript, and we'd prefer to avoid that complexity if possible. Others don't really describe what they're doing so it becomes difficult to follow.

What am I doing wrong to cause this table to act in such a way?

.fixed_header_watchers tbody {
  display: block;
  overflow: auto;
  height: 300px;
  width: 100%;
}

.fixed_header_watchers th {
  top: 0;
  position: sticky;
  padding: 0 0px;
  border-collapse: collapse;
  font-size: 100%;
  border: none;
  white-space: nowrap;
  padding-left: 5px;
}

* {
  padding: 0;
  margin: 0;
}
<div id='watchers'>
  <table class='fixed_header_watchers'>
    <tr>
      <th colspan=2>Watcher</th>
      <th>Joined</th>
      <th>Latest</th>
    </tr>
    <tr>
      <td style='text-align:right;'>40</td>
      <td>Wrestler<br>@wrestler</td>
      <td>08:18</td>
      <td style='text-align:right;'>0.3m</td>
    </tr>
    <tr>
      <td style='text-align:right;'>39</td>
      <td>Allen Evans - AME Arts</td>
      <td>08:16</td>
      <td style='text-align:right;'>0.0m</td>
    </tr>
    <tr>
      <td style='text-align:right;'>38</td>
      <td>Ct Pirate<br>@Missing</td>
      <td>08:13</td>
      <td style='text-align:right;'>0.0m</td>
    </tr>
    <tr>
      <td style='text-align:right;'>37</td>
      <td>Will</td>
      <td>08:12</td>
      <td style='text-align:right;'>0.0m</td>
    </tr>
    <tr>
      <td style='text-align:right;'>36</td>
      <td>Derth Of 13<br>Sep 11 22, 56.0 weeks</td>
      <td>08:11</td>
      <td style='text-align:right;'>5.9m</td>
    </tr>
    <tr>
      <td style='text-align:right;'>35</td>
      <td>Joy Robbins<br>@joyorobbins6838</td>
      <td>08:11</td>
      <td style='text-align:right;'>6.7m</td>
    </tr>
    <tr>
      <td style='text-align:right;'>34</td>
      <td>Spanky<br>@spanky</td>
      <td>08:07</td>
      <td style='text-align:right;'>11.2m</td>
    </tr>
    <tr>
      <td style='text-align:right;'>33</td>
      <td>Bob Crawford Art<br>Sep 24 23, 2.0 weeks</td>
      <td>08:04</td>
      <td style='text-align:right;'>0.0m</td>
    </tr>
    <tr>
      <td style='text-align:right;'>32</td>
      <td>(Barkley)107 Drifters</td>
      <td>08:03</td>
      <td style='text-align:right;'>0.0m</td>
    </tr>
  </table>

1

There are 1 best solutions below

0
Mark Schultheiss On

This is a slight bit of overkill just to illustrate.

Here I use a grid layout - tables are kind of a grid after all.

I added some ugly borders just to show what is where - they can be removed.

Basically I define a super-centered body and #watchers.display: grid; place-items: center;

Then I start defining grids within grids so we can customize things and size them. Note how I gave the header and body the same column widths grid-template-columns: 2em 20ch 8ch 6ch; but place the first header within the first two columns. with first name, primarily just for clarity of use; that definition could be combined in one CSS block also.

I arbitrarily assigned the tbody a height: 10em; just for this example.

A good bit of this is just some sizing like 3em for the body row height, things like that.

Side note not a fan of <br> and those two text segments might be in spans to make styling work using a more flexible set of CSS - for example give them a different layout depending upon the view size etc.

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-size: 16px;
}

body,
#watchers {
  display: grid;
  place-items: center;
}

.fixed_header_watchers {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 3em auto;
  grid-template-areas: "head" "body";
  align-items: center;
  border: 1px red solid;
}

.fixed_header_watchers thead {
  grid-area: head;
  position: sticky;
  top: 0;
}

.fixed_header_watchers tbody {
  grid-area: body;
  overflow: auto;
  height: 10em;
}

.fixed_header_watchers thead,
.fixed_header_watchers tbody {
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: auto;
  align-items: center;
  border: 1px blue solid;
  margin: 0.25em;
}

.fixed_header_watchers thead .header-row {
  display: grid;
  grid-template-columns: 2em 20ch 8ch 6ch;
  grid-template-rows: 1fr;
  grid-template-areas: "first first second third";
  border: solid green 1px;
}

.fixed_header_watchers thead .header-row th:first-of-type {
  grid-area: first;
}

.fixed_header_watchers thead .header-row th {
  border: solid #00ff00A0 2px;
}

.fixed_header_watchers tbody tr {
  display: grid;
  grid-template-columns: 2em 20ch 8ch 6ch;
  grid-template-rows: 3em;
  align-items: center;
  border: solid orange 1px;
  margin: 0.25em;
}

.fixed_header_watchers tbody tr td {
  border: solid purple 1px;
  text-align: center;
}

.fixed_header_watchers * tr *.right-me {
  text-align: right;
}
<div id='watchers'>
  <table class='fixed_header_watchers'>
    <thead>
      <tr class="header-row">
        <th>Watcher</th>
        <th>Joined</th>
        <th>Latest</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="right-me">40</td>
        <td>Wrestler<br>@wrestler</td>
        <td>08:18</td>
        <td class="right-me">0.3m</td>
      </tr>
      <tr>
        <td class="right-me">39</td>
        <td>Allen Evans - AME Arts</td>
        <td>08:16</td>
        <td class="right-me">0.0m</td>
      </tr>
      <tr>
        <td class="right-me">38</td>
        <td>Ct Pirate<br>@Missing</td>
        <td>08:13</td>
        <td class="right-me">0.0m</td>
      </tr>
      <tr>
        <td class="right-me">37</td>
        <td>Will</td>
        <td>08:12</td>
        <td class="right-me">0.0m</td>
      </tr>
      <tr>
        <td class="right-me">36</td>
        <td>Derth Of 13<br>Sep 11 22, 56.0 weeks</td>
        <td>08:11</td>
        <td class="right-me">5.9m</td>
      </tr>
      <tr>
        <td class="right-me">35</td>
        <td>Joy Robbins<br>@joyorobbins6838</td>
        <td>08:11</td>
        <td class="right-me">6.7m</td>
      </tr>
      <tr>
        <td class="right-me">34</td>
        <td>Spanky<br>@spanky</td>
        <td>08:07</td>
        <td class="right-me">11.2m</td>
      </tr>
      <tr>
        <td class="right-me">33</td>
        <td>Bob Crawford Art<br>Sep 24 23, 2.0 weeks</td>
        <td>08:04</td>
        <td class="right-me">0.0m</td>
      </tr>
      <tr>
        <td class="right-me">32</td>
        <td>(Barkley)107 Drifters</td>
        <td>08:03</td>
        <td class="right-me">0.0m</td>
      </tr>
    </tbody>
  </table>
</div>