I have a table with fixed first three columns. My first three columns are fixed. What I want is that the rest columns should calculate their height and width automatically, based on their content.
So my CSS looks like this:
.outer {
position:relative;
background-color: hotpink;
}
.inner {
overflow-x:scroll;
overflow-y: visible;
width: calc(100% - 1500px);
margin-left: 18em;
background-color: greenyellow;
}
table {
table-layout: fixed;
width: 100%;
}
td, th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 0.8em;
width: 150px;
height: 42px;
word-break: break-all;
}
.col1 {
position:absolute;
left: 0em;
width: 6em;
}
.col2 {
position:absolute;
left: 6em;
width: 6em;
}
.col3 {
position:absolute;
left: 12em;
width: 6em;
}
.emptyrow {
position:absolute;
left: 0em;
width: 18em;
}
This is HTML of table:
<div class="outer">
<div class="inner">
<table>
<thead>
<tr>
<th class="col1">Header 1</th>
<th class="col2">Header 2</th>
<th class="col3">Header 3</th>
<th>Header 4</th>
<th>Header 5</th>
<th>Header 6</th>
<th>Header 7</th>
</tr>
</thead>
<tbody>
<tr>
<td class="emptyrow">This column should have value.</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td class="col1">col 1 - B</td>
<td class="col2">col 2 - B</td>
<td class="col3">col 3 - B</td>
<td>col 4 - B</td>
<td>col 5 - B</td>
<td>col 6 - B</td>
<td>col 7 - B</td>
</tr>
<tr>
<td class="col1">col 1 - C</td>
<td class="col2">col 2 - C</td>
<td class="col3">col 3 - C</td>
<td>col 4 - C</td>
<td>col 5 - C</td>
<td>col 6 - C</td>
<td>col 7 - C</td>
</tr>
</tbody>
</table>
</div>
</div>
Is it possible to avoid setting width and height in td, th?
I mean to avoid this hardcoded values of height and width:
td, th {
...
width:6em;
height: 4em;
}
Is it possible to do just with plain CSS without JavaScript? If it is not possible to implement with this solution, it would be really great to see another approach.
Requirements:
Padding in all columns should be 0.8emWidth and Height of td, th should not be hardcoded. They should fit to content.border-top: 1px solid #ccc; this property should be keptAn image of what it is desirable to see:

I'm afraid that there isn't any perfect solution.
I will give you different options and you need to decide what to give up.
✅ Pure CSS - ✅ Old browsers - ❌ Flexible width - ❌ Proper highlight
The key is to set a fixed width for the first three columns and then set a margin-left property of the .wrapped element equal to the total width of the columns.
.wrapper {
overflow-x: scroll;
overflow-y: visible;
margin-left: 18em;
width: 15em;
background-color: greenyellow;
}
td,
th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 0.8em;
}
th, tr:nth-of-type(1) td {
height: 1em;
}
th:nth-of-type(1),
td:nth-of-type(1) {
position: absolute;
left: 0em;
width: 6em;
}
th:nth-of-type(2),
td:nth-of-type(2) {
position: absolute;
left: 6em;
width: 6em;
}
th:nth-of-type(3),
td:nth-of-type(3) {
position: absolute;
left: 12em;
width: 6em;
}
tr:hover td {
background-color: yellow;
}
<div class="wrapper">
<table>
<thead>
<tr>
<th>Header-1</th>
<th>Header-2</th>
<th>Header-3</th>
<th>Header-4</th>
<th>Header-5</th>
<th>Header-5</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>col 1 - A</td>
<td>col 2 - A</td>
<td>col 3 - A</td>
<td>col 4 - A (WITH LONGER CONTENT)</td>
<td>col 5 - A</td>
<td>col 6 - A</td>
</tr>
<tr>
<td>col 1 - B</td>
<td>col 2 - B</td>
<td>col 3 - B</td>
<td>col 4 - B</td>
<td>col 5 - B</td>
<td>col 6 - B</td>
</tr>
<tr>
<td>col 1 - C</td>
<td>col 2 - C</td>
<td>col 3 - C</td>
<td>col 4 - C</td>
<td>col 5 - C</td>
<td>col 6 - C (WITH_A_LONG_WORD)</td>
</tr>
</tbody>
</table>
❌ Pure CSS - ✅ Old browsers - ✅ Flexible width - ❌ Proper highlight
Here, instead of hard-coding the width of the columns beforehand, we use JS to calculate and set the width.
A much better JS implementation is possible. Take it just as an example.
let wrapper = document.querySelector('.wrapper');
let cols = document.querySelectorAll('th');
let widthCol0 = cols[0].offsetWidth;
let widthCol1 = cols[1].offsetWidth;
let widthCol2 = cols[2].offsetWidth;
stylesheet = document.styleSheets[0]
stylesheet.insertRule('th:nth-of-type(1), td:nth-of-type(1) { left: 0px; position: absolute; width: ' + widthCol0 + 'px;}', 0);
stylesheet.insertRule('th:nth-of-type(2), td:nth-of-type(2) { left: ' + widthCol0 + 'px; position: absolute; width: ' + widthCol1 + 'px;}', 0);
stylesheet.insertRule('th:nth-of-type(3), td:nth-of-type(3) { left: ' + (widthCol0 + widthCol1) + 'px; position: absolute; width: ' + widthCol2 + 'px;}', 0);
wrapper.style.marginLeft = (widthCol0 + widthCol1 + widthCol2) + 'px';
.wrapper {
overflow-x: scroll;
overflow-y: visible;
width: 15em;
background-color: greenyellow;
}
td,
th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 0.8em;
}
th, tr:nth-of-type(1) td {
height: 1em;
}
tr:hover td {
background-color: yellow;
}
<div class="wrapper">
<table>
<thead>
<tr>
<th>Header-1</th>
<th>Header-2</th>
<th>Header-3</th>
<th>Header-4</th>
<th>Header-5</th>
<th>Header-5</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>col 1 - A (WITH LONGER CONTENT)</td>
<td>col 2 - A</td>
<td>col 3 - A</td>
<td>col 4 - A (WITH LONGER CONTENT)</td>
<td>col 5 - A</td>
<td>col 6 - A</td>
</tr>
<tr>
<td>col 1 - B</td>
<td>col 2 - B</td>
<td>col 3 - B</td>
<td>col 4 - B</td>
<td>col 5 - B</td>
<td>col 6 - B</td>
</tr>
<tr>
<td>col 1 - C</td>
<td>col 2 - C (WITH_A_LONG_WORD)</td>
<td>col 3 - C</td>
<td>col 4 - C</td>
<td>col 5 - C</td>
<td>col 6 - C (WITH_A_LONG_WORD)</td>
</tr>
</tbody>
</table>
✅ Pure CSS - ❌ Old browsers - ❌ Flexible width - ✅ Proper highlight
I really like the position: sticky solution that @Dominic has shown. It is the way to go if you don't need to support IE.
Dropping IE support pays back. You can have the proper highlight and the width of the first three columns will adapt to the content.
Still, even when you don't need to hard-code a width, you need to hard-code a left property to set at which point the columns become sticky. That kind of defeats the point of having a flexible width. There is no way around.
.wrapper {
width: 40em;
overflow-x: scroll;
}
td,
th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 0.8em;
}
th,
tr:nth-of-type(1) td {
height: 1em;
}
th:nth-of-type(1), td:nth-of-type(1),
th:nth-of-type(2), td:nth-of-type(2),
th:nth-of-type(3), td:nth-of-type(3) {
background-color: white;
position: sticky;
}
th:nth-of-type(1), td:nth-of-type(1) {
left: 0em;
}
th:nth-of-type(2), td:nth-of-type(2) {
left: 6em;
}
th:nth-of-type(3), td:nth-of-type(3) {
left: 12em;
}
tr:hover td{
background-color: yellow;
}
<div class="wrapper">
<table>
<thead>
<tr>
<th>Header-1</th>
<th>Header-2</th>
<th>Header-3</th>
<th>Header-4</th>
<th>Header-5</th>
<th>Header-6</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>col 1 - A (WITH LONGER CONTENT)</td>
<td>col 2 - A</td>
<td>col 3 - A</td>
<td>col 4 - A (WITH LONGER CONTENT)</td>
<td>col 5 - A</td>
<td>col 6 - A</td>
</tr>
<tr>
<td>col 1 - B</td>
<td>col 2 - B</td>
<td>col 3 - B</td>
<td>col 4 - B</td>
<td>col 5 - B</td>
<td>col 6 - B</td>
</tr>
<tr>
<td>col 1 - C</td>
<td>col 2 - C (WITH_A_LONG_WORD)</td>
<td>col 3 - C</td>
<td>col 4 - C</td>
<td>col 5 - C</td>
<td>col 6 - C (WITH_A_LONG_WORD)</td>
</tr>
</tbody>
</table>
</div>
❌ Pure CSS - ❌ Old browsers - ✅ Flexible width - ✅ Proper highlight
This example has exactly the same HTML and CSS than the one above.
Like in our second example, we use JS to calculate the width of the columns. In this case we use it to override the left property. Also, the JS code is more straightforward.
Why we set the left property in CSS if we are going to set it later with JS? Because if the client doesn't run JS we don't want the columns collapsing completely, breaking our layout.
let cols = document.querySelectorAll('th');
let widthCol0 = cols[0].offsetWidth;
let widthCol1 = cols[1].offsetWidth;
stylesheet = document.styleSheets[0];
stylesheet.insertRule('th:nth-of-type(2), td:nth-of-type(2) { left: ' + widthCol0 + 'px !important;}', 0);
stylesheet.insertRule('th:nth-of-type(3), td:nth-of-type(3) { left: ' + (widthCol0 + widthCol1) + 'px !important;', 0);
.wrapper {
width: 40em;
overflow-x: scroll;
}
td,
th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 0.8em;
}
th,
tr:nth-of-type(1) td {
height: 1em;
}
th:nth-of-type(1), td:nth-of-type(1),
th:nth-of-type(2), td:nth-of-type(2),
th:nth-of-type(3), td:nth-of-type(3) {
background-color: white;
position: sticky;
}
th:nth-of-type(1), td:nth-of-type(1) {
left: 0em;
}
th:nth-of-type(2), td:nth-of-type(2) {
left: 6em;
}
th:nth-of-type(3), td:nth-of-type(3) {
left: 12em;
}
tr:hover td{
background-color: yellow;
}
<div class="wrapper">
<table>
<thead>
<tr>
<th>Header-1</th>
<th>Header-2</th>
<th>Header-3</th>
<th>Header-4</th>
<th>Header-5</th>
<th>Header-6</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>col 1 - A (WITH LONGER CONTENT)</td>
<td>col 2 - A</td>
<td>col 3 - A</td>
<td>col 4 - A (WITH LONGER CONTENT)</td>
<td>col 5 - A</td>
<td>col 6 - A</td>
</tr>
<tr>
<td>col 1 - B</td>
<td>col 2 - B</td>
<td>col 3 - B</td>
<td>col 4 - B</td>
<td>col 5 - B</td>
<td>col 6 - B</td>
</tr>
<tr>
<td>col 1 - C</td>
<td>col 2 - C (WITH_A_LONG_WORD)</td>
<td>col 3 - C</td>
<td>col 4 - C</td>
<td>col 5 - C</td>
<td>col 6 - C (WITH_A_LONG_WORD)</td>
</tr>
</tbody>
</table>
</div>
It is up to you to decide which approach best fits your needs.
Personally, I find the 4th the most powerful because:
If JS is disabled the columns will collapse beyond the optimal point, but everything will work properly. Just play with the default left property in the CSS.
Under IE11, the table will nicely fall back to a non-fixed columns table.
I don't think that's a big deal. Of course, it you are programming for a intranet where IE11 is the browser of choice, take the first or the second approach.
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