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>
Select the column that's immediately to the right of the last column you want frozen. Select the View tab, Windows Group, click the Freeze Panes drop down and select Freeze Panes. Excel inserts a thin line to show you where the frozen pane begins.
Set a to zero and b to the position of the column in the table, e.g. td:nth-child(2) { text-align: right; } to right-align the second column. If the table does use a colspan attribute, the effect can be achieved by combining adequate CSS attribute selectors like [colspan=n] , though this is not trivial.
In general, Excel is considered the grand-daddy of data grid applications, and most apps with data tables generally follow it's basic defaults. So, titles, dates and text usually align to the left and numerical values generally align to the right. Column headers always align to the left regardless of data type.
To freeze the row/column we can use a simple HTML table and CSS. HTML: In HTML we can define the header row by <th> tag or we can use <td> tag also. Below example is using the <th> tag. We also put the table in DIV element to see the horizontal and vertical scrollbar by setting the overflow property of the DIV element.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With