Reputation: 1120
After much experimenting, I finally came up with fairly simple HTML that displays a table the way I want: 6 columns with specific widths, with two columns right-justified, scrolling under a fixed header row. However, the headers don't quite align to the columns:
How can I make the headers line up exactly with the data columns? My online searches have found this to be a common problem, but with no real explanation of the cause or a known simple fix.
My HTML is below. The columns widths are are magic numbers because of the data that will eventually be displayed. Making the header text normal instead of bold, or even empty headers, has no effect on alignment.
If besides solving the alignment issue you also have a more simple way of defining a table with the same features, please let me know.
Edit: box-sizing: border-box;
as recommended by ProllyGeek seemed promising because it works on the sample data above,
but using different cell data still causes alignment to be slightly off
as shown here (the Price/Drops, Drops/Description, and Item#/Posted
column borders are off even when using border-box
):
There are dozens if not hundreds of posts wanting sticky headers over scrollable rows, but apparently all solutions avoid dynamic columns, often using color to hide the alignment issue, or an enclosing div that has to know the table width to show a scrollbar properly positioned alongside dynamic column widths. Most examples ignore column widths and just use 100% wide tables with oversized columns.
There appears to be no known automatic solution for a sticky header, with dynamic column widths (no declared table width), over scrollable rows, with precise header and column alignment, using just pure CSS/HTML.
I'm just going to hide misalignments using thead { background-color: black; color: white; }
.
<style>
table {
width: 688px; /* 688 = column widths 80 + 56 + 280 + 120 + 56 + 96 */
table-layout: fixed;
font: 12px Courier;
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
padding: 5px;
}
tbody {
display: block;
width: 703px; /* 703 = 688 table width + 15 extra for scrollbar */
overflow-y: scroll;
height: 65px;
}
thead tr { display: block; }
tr:nth-child(even) { background-color: #eee; }
th:nth-child(1), td:nth-child(1) {text-align: right; width: 80px; }
th:nth-child(2), td:nth-child(2) {text-align: left; width: 56px; }
th:nth-child(3), td:nth-child(3) {text-align: left; width: 280px; }
th:nth-child(4), td:nth-child(4) {text-align: left; width: 120px; }
th:nth-child(5), td:nth-child(5) {text-align: right; width: 56px; }
th:nth-child(6), td:nth-child(6) {text-align: left; width: 96px; }
</style>
<table>
<thead>
<tr>
<th>Price</th>
<th>Drops</th>
<th>Description</th>
<th>Category</th>
<th>Item #</th>
<th>Posted</th>
</tr>
</thead>
<tbody>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
</tbody>
</table>
Upvotes: 13
Views: 3820
Reputation: 1249
First I added box-sizing: border;
for all the tags *
then set the width
property for the first 3 child of th
and td
separately with different values.
Also, I removed the border from table
so the borders
of th
and td
doesn't add up & goes out of the box.
That's all I did in your current snippet, hope it would help.
<style>
* {box-sizing: border-box}
table {
width: 688px; /* 688 = column widths 80 + 56 + 280 + 120 + 56 + 96 */
table-layout: fixed;
font: 12px Courier;
border-collapse: collapse;
padding: 5px;
}
th, td {
border: 1px solid black;
padding: 5px;
}
tbody {
display: block;
width: 703px; /* 703 = 688 table width + 15 extra for scrollbar */
overflow-y: scroll;
height: 65px;
}
thead tr { display: block; }
tr:nth-child(even) { background-color: #eee; }
th:nth-child(1) {text-align: right; width: 79px;}
th:nth-child(2) {text-align: left; width: 56px;}
th:nth-child(3) {text-align: left; width: 278px;}
td:nth-child(1) {text-align: right; width: 80px; }
td:nth-child(2) {text-align: left; width: 56px; }
td:nth-child(3) {text-align: left; width: 280px; }
th:nth-child(4), td:nth-child(4) {text-align: left; width: 120px; }
th:nth-child(5), td:nth-child(5) {text-align: right; width: 56px; }
th:nth-child(6), td:nth-child(6) {text-align: left; width: 96px; }
</style>
<table>
<thead>
<tr>
<th>Price</th>
<th>Drops</th>
<th>Description</th>
<th>Category</th>
<th>Item #</th>
<th>Posted</th>
</tr>
</thead>
<tbody>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
</tbody>
</table>
Upvotes: 0
Reputation: 3659
This is not a solution to your problem with fixed widths, but a step toward your desire for
a sticky header, with dynamic column widths (no declared table width), over scrollable rows, with precise header and column alignment, using just pure CSS/HTML.
I believe it's faster for large tables to be rendered with dynamic column widths,
header/column alignment is then guaranteed,
plus the code is no longer cluttered with magic numbers.
Adding scrolling requires using an enclosing <div>
like the snippet below.
UPDATE: Per your comment request, here's a dynamic table (no declared column widths) with a scrolling div that automatically repositions the scrollbar as necessary. Click the "Refill table" button to generate test tables.
const SCROLLBARWIDTH = 16;
let maxWidth = 5; // initial max width of our dummy strings
function dummyString() {
return 'x'.repeat(Math.floor((Math.random() * maxWidth)+1)); // dummy data that will tend to widen with each refill
}
function refill() {
document.getElementById("theBody").innerHTML = '<tr></tr>'; // remove any previous table body
let tableRef = document.getElementById("theTable"); // our scrollable HTML table that we'll add rows to
for (let i = 0; i < 20; i++) { // create 20 rows of dummy data
let newRow = tableRef.insertRow(-1); // add a new row to the table, then set the 6 <td> cells...
newRow.insertCell(-1).appendChild(document.createTextNode(dummyString())); // price
newRow.insertCell(-1).appendChild(document.createTextNode(dummyString())); // drops
newRow.insertCell(-1).appendChild(document.createTextNode(dummyString())); // description
newRow.insertCell(-1).appendChild(document.createTextNode(dummyString())); // category
newRow.insertCell(-1).appendChild(document.createTextNode(dummyString())); // item #
newRow.insertCell(-1).appendChild(document.createTextNode(dummyString())); // posted
}
let tableWidth = window.getComputedStyle(tableRef).getPropertyValue('width'); // get resulting dynamic table width
document.getElementById("container").style.width = parseInt(tableWidth) + 1 + SCROLLBARWIDTH + 'px'; // make scrolling div wide enough
maxWidth++; // so our next refill will tend to have wider dummy strings
}
refill();
#container { overflow-y: scroll; height: 200px; } /* width to be determined after table filled */
thead th { position: sticky; top: 0; }
table { font: 12px Courier; border-collapse: collapse; }
th { background: black; color: white; } /* hide scrolling behind the header */
table, th, td { border: 1px solid black; padding: 5px; }
<button onclick="refill()">Refill table</button> <!-- click to generate wider table -->
<div id="container">
<table id="theTable">
<thead>
<tr>
<th>Price</th>
<th>Drops</th>
<th>Description</th>
<th>Category</th>
<th>Item #</th>
<th>Posted</th>
</tr>
</thead>
<tbody id="theBody">
<tr>
</tr>
</tbody>
</table>
</div>
Upvotes: 2
Reputation: 1768
So here's my take:
box-sizing: border-box
to both td
and th
tbody
(which is set to display: block
to enable overflow scrolling), apply the same display: block
to thead
(which wraps table headers). Table elements has complex structure and the initial display
value of thead
is table-header-group
so there might be some differences when calculating the width (that's my guess, would love to hear detail clarification from someone), so the idea here is to make thead
to display as the same with tbody
.If you're using trackpad, this solution works fine, it preserves the alignment and the column width even with longer cell content. For the scrollbar appearing when you plug your mouse in, I think I will go with setting display: none
to the scrollbar, it's a trade-off :/ (don't know any robust solution for that ugly scrollbar to not add up the width)
<style>
table {
width: 688px; /* 688 = column widths 80 + 56 + 280 + 120 + 56 + 96 */
border: 1px solid black;
table-layout: fixed;
font: 12px Courier;
border-collapse: collapse;
}
td, th {
border: 1px solid black;
padding: 5px;
box-sizing: border-box;
}
tbody {
display: block;
/* width: 688px; */
overflow-y: scroll;
height: 65px;
}
tbody::-webkit-scrollbar {
display: none;
}
thead { display: block; }
tr:nth-child(even) { background-color: #eee; }
th:nth-child(1), td:nth-child(1) {text-align: right; width: 80px; }
th:nth-child(2), td:nth-child(2) {text-align: left; width: 56px; }
th:nth-child(3), td:nth-child(3) {text-align: left; width: 280px; }
th:nth-child(4), td:nth-child(4) {text-align: left; width: 120px; }
th:nth-child(5), td:nth-child(5) {text-align: right; width: 56px; }
th:nth-child(6), td:nth-child(6) {text-align: left; width: 96px; }
</style>
<table>
<thead>
<tr>
<th>Price</th>
<th>Drops</th>
<th>Description</th>
<th>Category</th>
<th>Item #</th>
<th>Posted</th>
</tr>
</thead>
<tbody>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item Valuable item Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
</tbody>
</table>
Upvotes: 0
Reputation: 666
I have tried to maintain the width of table header. Your design view broken because of border css. I have used "position:sticky" to Table header and gave a new div as parent of table and css is "overflow: auto;". Please have a look...
.tableDiv {
height: 68px;
overflow-y: auto;
width: calc(688px + 15px);
}
table {
width: 688px;
table-layout: fixed;
font: 12px Courier;
border-collapse: collapse;
border: 1px solid black;
}
table th {
border: 1px solid black;
position: sticky;
top: 0;
background: #fff;
}
table td {
border: 1px solid black;
padding: 5px;
box-sizing: border-box;
}
tbody {
width: 688px;
}
tr:nth-child(even) { background-color: #eee; }
<div class="tableDiv">
<table>
<thead>
<tr>
<th>Price</th>
<th>Drops</th>
<th>Description</th>
<th>Category</th>
<th>Item #</th>
<th>Posted</th>
</tr>
</thead>
<tbody>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
<tr>
<td>$5.00<td>Oct 10<td>Valuable item<td>Miscellaneous<td>1234<td>Sep 10 2020</tr>
</tbody>
</table>
</div>
Upvotes: 0
Reputation: 450
There are several issues: the scrollbar applied to tbody and the sizes that are not equal for table and tbody because of the borders.
Instead, here is my proposal:
I apply the scrollbar to the table, which I transform into a block to be able to set a height
and an overflow
. I make thead
sticky
. I remove all styles from tbody
including fixed size.
table {
font: 12px Courier;
border-collapse: separate;
border-spacing: 0;
display: block;
width: 772px;
overflow-y: scroll;
max-height: 65px;
}
thead {
background: white;
display: block;
position: sticky;
top: 0; /* Required for the stickiness */
}
th,
td {
border: 1px solid black;
padding: 5px;
}
tr:nth-child(even) {
background-color: #eee;
}
th:nth-child(1),
td:nth-child(1) {
text-align: right;
width: 80px;
}
th:nth-child(2),
td:nth-child(2) {
text-align: left;
width: 56px;
}
th:nth-child(3),
td:nth-child(3) {
text-align: left;
width: 280px;
}
th:nth-child(4),
td:nth-child(4) {
text-align: left;
width: 120px;
}
th:nth-child(5),
td:nth-child(5) {
text-align: right;
width: 56px;
}
th:nth-child(6),
td:nth-child(6) {
text-align: left;
width: 96px;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<table>
<thead>
<tr>
<th>Price</th>
<th>Drops</th>
<th>Description</th>
<th>Category</th>
<th>Item #</th>
<th>Posted</th>
</tr>
</thead>
<tbody>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
</tbody>
</table>
</body>
</html>
Edit:
For me it remains a problem of width:
I no longer have this border problem by setting the width of the table to:
If you need to keep 688px wide, you need to reduce the size of the cells (width or padding). Otherwise, you have to increase the width of the table.
I updated the code above.
Upvotes: 0
Reputation: 2033
You wrapped <td>
in <tr>
. See W3's docs for correct syntax.
body {
margin: 0;
}
table {
max-width: 763px;
font: 12px Courier;
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
padding: 5px;
}
tr:nth-child(even) {
background-color: #eee;
}
<table>
<tr>
<th>Price</th>
<th>Drops</th>
<th>Description</th>
<th>Category</th>
<th>Item #</th>
<th>Posted</th>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
</table>
Upvotes: 0
Reputation: 3651
I got the borders to align by removing the display:block
properties. But without the display property, you cannot set a height to a HTML table. The only workaround I could think of was to wrap the whole table in a div and assign a max-height
property to the div.
I tried with a sticky header but the table body would scroll behind it and that does not look pretty. You can always block out the body content by adding a background color to the table header th cells.
Maybe someone else might have a better solution to hiding the body content when scrolling.
table {
width: 688px;
/* 688 = column widths 80 + 56 + 280 + 120 + 56 + 96 */
table-layout: fixed;
font: 12px Courier;
border-collapse: collapse;
}
table,
th,
td {
border: 1px solid black;
padding: 5px;
}
tbody {
/*display: block;*/
width: 703px;
/* 703 = 688 table width + 15 extra for scrollbar */
overflow-y: scroll;
height: 65px;
z-index: 1;
}
/*thead tr { display: block; } */
tr:nth-child(even) {
background-color: #eee;
}
th:nth-child(1),
td:nth-child(1) {
text-align: right;
width: 80px;
}
th:nth-child(2),
td:nth-child(2) {
text-align: left;
width: 56px;
}
th:nth-child(3),
td:nth-child(3) {
text-align: left;
width: 280px;
}
th:nth-child(4),
td:nth-child(4) {
text-align: left;
width: 120px;
}
th:nth-child(5),
td:nth-child(5) {
text-align: right;
width: 56px;
}
th:nth-child(6),
td:nth-child(6) {
text-align: left;
width: 96px;
}
.tableContent {
width: fit-content;
border: 1px solid rgb(127, 127, 127);
max-height: 70px;
overflow: auto;
}
td {
word-wrap: break-word
}
th {
position: sticky;
top: 0;
z-index: 1;
background-color: lightsteelblue;
}
<div class="tableContent">
<table>
<thead>
<tr>
<th>Price</th>
<th>Drops</th>
<th>Description</th>
<th>Category</th>
<th>Item #</th>
<th>Posted</th>
</tr>
</thead>
<tbody>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable itemaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
<tr>
<td>$5.00</td>
<td>Oct 10</td>
<td>Valuable item</td>
<td>Miscellaneous</td>
<td>1234</td>
<td>Sep 10 2020</td>
</tr>
</tbody>
</table>
</div>
EDIT:
Just noticed the part about the variable length data in the cells. Modified my code
Upvotes: 0
Reputation: 253
I would suggest using min, max-width instead of width. Also, you added all the column width, but every column has a padding of 5px, which adds 5px on left and 5px on right. So, make sure you add them up.
Here is the fix:
table {
/* width: 688px; 688 = column widths 80 + 56 + 280 + 120 + 56 + 96 + 60(padding)*/
min-width: 748px;
max-width: 763px;
table-layout: fixed;
font: 12px Courier;
border-collapse: collapse;
}
table,
th,
td {
border: 1px solid black;
padding: 5px;
}
tbody {
display: block;
/* max-width: 763px; */
/* 763 = 688 table width + 60(padding) + 15 extra for scrollbar */
overflow-y: scroll;
height: 65px;
}
thead tr {
display: block;
}
tr:nth-child(even) {
background-color: #eee;
}
/* th:nth-child(1), td:nth-child(1) {text-align: right; width: 80px; }
th:nth-child(2), td:nth-child(2) {text-align: left; width: 56px; }
th:nth-child(3), td:nth-child(3) {text-align: left; width: 280px; }
th:nth-child(4), td:nth-child(4) {text-align: left; width: 120px; }
th:nth-child(5), td:nth-child(5) {text-align: right; width: 56px; }
th:nth-child(6), td:nth-child(6) {text-align: left; width: 96px; } */
th:nth-child(1),
td:nth-child(1) {
text-align: right;
min-width: 80px;
max-width: 80px;
}
th:nth-child(2),
td:nth-child(2) {
text-align: left;
min-width: 56px;
max-width: 56px;
}
th:nth-child(3),
td:nth-child(3) {
text-align: left;
min-width: 280px;
max-width: 280px;
}
th:nth-child(4),
td:nth-child(4) {
text-align: left;
min-width: 120px;
max-width: 120px;
}
th:nth-child(5),
td:nth-child(5) {
text-align: right;
min-width: 56px;
max-width: 56px;
}
th:nth-child(6),
td:nth-child(6) {
text-align: left;
min-width: 96px;
max-width: 96px;
}
.lastColumn {
min-width: 114px !important;
}
<table>
<thead>
<tr>
<th>Price</th>
<th>Drops</th>
<th>Description</th>
<th>Category</th>
<th>Item #</th>
<th class="lastColumn">Posted</th>
</tr>
</thead>
<tbody>
<tr>
<td>$5.00
<td>Oct 10
<td>Valuable item
<td>Miscellaneous
<td>1234
<td>Sep 10 2020
</tr>
<tr>
<td>$5.00
<td>Oct 10
<td>Valuable item
<td>Miscellaneous
<td>1234
<td>Sep 10 2020
</tr>
<tr>
<td>$5.00
<td>Oct 10
<td>Valuable item
<td>Miscellaneous
<td>1234
<td>Sep 10 2020
</tr>
<tr>
<td>$5.00
<td>Oct 10
<td>Valuable item
<td>Miscellaneous
<td>1234
<td>Sep 10 2020
</tr>
</tbody>
</table>
You just need the last column to take the remaining width. So, specify to that column.
Upvotes: 2
Reputation: 15856
Solution is very simple just change:
table, th, td {
border: 1px solid black;
padding: 5px;
}
to
table, th, td {
border: 1px solid black;
padding: 5px;
box-sizing: border-box;
}
Upvotes: 0