0

I have two html tables. The first table is for the header and the second table is for the body. The reason for having two separate table is to be able to have a fixed header and a scrollable body.

How will I make the table header aligned with the table body?

Below is my code. current output

HTML

.scroll {
  height: 100px;
  overflow: auto;
}

#containerdiv {
  width: 80%;
  border: 2px solid brown;
}
<div id="containerdiv">
  <div class="header">
    <table>
      <thead>
        <!-- column names are going to be generated automatially with angular -->
        <tr>
          <th style="border: red solid 2px">Id</th>
          <th style="border: red solid 2px">Customer Name</th>
          <th style="border: red solid 2px">Address</th>
          <th style="border: red solid 2px">Phone</th>
        </tr>
      </thead>
      <!-- this is to set size for the column headers-->
      <tbody>
        <tr>
          <td style="width:2%;"></td>
          <td style="width:5%;"></td>
          <td style="width:6%;"></td>
          <td style="width:4%;"></td>
        </tr>
      </tbody>
    </table>
  </div>

  <div class="scroll">
    <table id="tablebody">
      <tr>
        <td style="width:4%;border: red solid 2px;">1</td>
        <td style="width:5%;border: red solid 2px;">Customer 1</td>
        <td style="width:6%;border: red solid 2px;">Address 1</td>
        <td style="width:4%;border: red solid 2px;">phone number 1</td>
      </tr>
      <tr>
        <td style="width:4%;border: red solid 2px;">2</td>
        <td style="width:5%;border: red solid 2px;">Customer 2</td>
        <td style="width:6%;border: red solid 2px;">Address 2</td>
        <td style="width:4%;border: red solid 2px;">phone number 2</td>
      </tr>
      <tr>
        <td style="width:4%;border: red solid 2px;">3</td>
        <td style="width:5%;border: red solid 2px;">Customer 3</td>
        <td style="width:6%;border: red solid 2px;">Address 3</td>
        <td style="width:4%;border: red solid 2px;">phone number 3</td>
      </tr>
      <tr>
        <td style="width:4%;border: red solid 2px;">4</td>
        <td style="width:5%;border: red solid 2px;">Customer 4</td>
        <td style="width:6%;border: red solid 2px;">Address 4</td>
        <td style="width:4%;border: red solid 2px;">phone number 4</td>
      </tr>
      <tr>
        <td style="width:4%;border: red solid 2px;">5</td>
        <td style="width:5%;border: red solid 2px;">Customer 5</td>
        <td style="width:6%;border: red solid 2px;">Address 5</td>
        <td style="width:4%;border: red solid 2px;">phone number 5</td>
      </tr>
    </table>

  </div>
</div>
Jayakrishnan
  • 1,295
  • 2
  • 13
  • 27
nainer
  • 23
  • 1
  • 5

2 Answers2

0

I would use one table and fix the header like below.

Note: in <th> tags, the text has to be placed twice: once in a <div> (this will be the fixed one) and once normally (this will stay inside the table structure and ensure column width). Latter one can be hidden with some additional styling.

thead th div {
  position: fixed;
}
<table>
  <thead>
    <tr>
      <th>
        <div>header 1 which is longer</div>
        header 1 which is longer
        <!-- yes, it's needed! -->
      </th>
      <th>
        <div>header 2</div>
        header 2
        <!-- yes, it's needed! -->
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
    <tr>
      <td>content 1</td>
      <td>content 2</td>
    </tr>
  </tbody>
</table>
juzraai
  • 5,693
  • 8
  • 33
  • 47
0

Your main problem is that your widths 1. don't match, and more importantly 2. don't make sense.

Note that in your header, the first column inside the tbody is set at width: 2%, but the equivalent column in the next table is at width: 4%. Those values are gonna have to match, or things won't line up.

But more importantly, assuming we change that 2% to 4%, your columns only add up to 19% wide.

Percentage widths are based on their parents, not on the overall page width. So since you have set #containerdiv to 80% wide, by setting a column to 4% wide, you're making that column 4% of 80% of the page width = 0.32% of the page width.

What you really want to do is think of the table as 100% wide, and divide your columns up so their widths add up to 100%.

#containerdiv {
  width: 80%;
}
#containerdiv table {
  width: 100%;
}
#containerdiv table td:nth-child(1) { /* first column */
  width: 15%;
}
/* and so on for other columns */

The other thing you can do to make this easier is to use box-sizing to include borders in the widths of your columns, so if you want a column to be, say, 25% wide with a 4 pixel border, you don't have to worry about setting a width like calc(25% - 4px). That is, if you want to continue with non-collapsed borders and spacing between table cells.

Lastly, note that your border shorthand property was out of order. It should be border-width, border-style, then border-color.

In this example, I took all your inline styles out and moved them into the stylesheet to make things easier to visualize, but if you're going to end up with inline styles in your app, that's fine. Follow the principles here, and it'll work:

.scroll {
  height: 100px;
  overflow: auto;
}
#containerdiv {
  width: 80%;
  border: 2px solid brown;
}
#containerdiv table {
  width: 100%;
}
.header table {
  padding-right: 17px;
}
#containerdiv table * {
  box-sizing: border-box;
}
#containerdiv th,
#containerdiv td {
  border: 2px solid red;
}
#containerdiv .header tbody td {
  border: none;
}
#containerdiv th:nth-child(1),
#containerdiv td:nth-child(1) {
  width: 15%;
}
#containerdiv th:nth-child(2),
#containerdiv td:nth-child(2) {
  width: 30%;
}
#containerdiv th:nth-child(3),
#containerdiv td:nth-child(3) {
  width: 30%;
}
#containerdiv th:nth-child(4),
#containerdiv td:nth-child(4) {
  width: 25%;
}
<div id="containerdiv">
  <div class="header">
    <table>
      <thead>
        <!-- column names are going to be generated automatially with angular -->
        <tr>
          <th>Id</th>
          <th>Customer Name</th>
          <th>Address</th>
          <th>Phone</th>
        </tr>
      </thead>
      <!-- this is to set size for the column headers-->
      <tbody>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
      </tbody>
    </table>
  </div>

  <div class="scroll">
    <table id="tablebody">
      <tr>
        <td>1</td>
        <td>Customer 1</td>
        <td>Address 1</td>
        <td>phone number 1</td>
      </tr>
      <tr>
        <td>2</td>
        <td>Customer 2</td>
        <td>Address 2</td>
        <td>phone number 2</td>
      </tr>
      <tr>
        <td>3</td>
        <td>Customer 3</td>
        <td>Address 3</td>
        <td>phone number 3</td>
      </tr>
      <tr>
        <td>4</td>
        <td>Customer 4</td>
        <td>Address 4</td>
        <td>phone number 4</td>
      </tr>
      <tr>
        <td>5</td>
        <td>Customer 5</td>
        <td>Address 5</td>
        <td>phone number 5</td>
      </tr>
    </table>
  </div>
</div>

Edit regarding scrollbar: I have fixed the scrollbar issue on PC by adding padding-right to the only first table, so that there is a gap to the right of it where the scrollbar appears next to the second table. I chose 17px as the value because that's the scrollbar width for the most common browsers on Windows. But note that this isn't a perfect solution because not all browsers' scrollbars are exactly the same width.

A real-life perfect execution of that approach would use JavaScript to detect the scrollbar width and then set the padding-right accordingly.

However, an alternate approach that doesn't require any JavaScript is to set the table widths in viewport units, for example, to 75vw.

That's because 100vw is equivalent to the entire width of the window, including the scrollbar, whereas 100% (on body) is equivalent to the window width to the left of the scrollbar. So basically using vw units would let you ignore the scrollbar so that both tables come out the same width. Just choose a value that's small enough to leave space for the scrollbar on the bottom table, and the top table will match, even though it doesn't have a scrollbar.

Finally, note that all of this is really just to get around problems you're causing for yourself by the overall approach you're using here of using two separate tables to achieve the fixed header. Really, in my opinion, you should only have one table and just freeze the thead in place and be done with it. Then you wouldn't even have the scrollbar issue. Revising the whole thing to work that way is beyond the scope of this question, but it's something for you to consider.

cjl750
  • 4,380
  • 3
  • 15
  • 27
  • I really appreciate your answer. I think the scrollbar makes the data in the phone column to move to the left, making it slightly out of order with the header. Is there a way to fix this? – nainer Sep 03 '17 at 04:55
  • Sorry but adding padding doesn't solve it. Increasing the width of the second table seems to work (like by 20px), but this will make the width of the second table wider than the container. – nainer Sep 03 '17 at 14:55
  • @nainer I got a chance to check this out on a PC. See latest edit. In general, the column widths issue discussed in the first part of the answer is 90% of your problem here and something to keep in mind on all tables in the future, but the scrollbar problem is something you kinda gotta come up with a one-off solution for in this particular case. – cjl750 Sep 04 '17 at 01:35