CSS: Fixed-first-column Table? (CSS only!) - html

I've been trying to figure out a way to take a standard table and create a fixed-column table where the first column is fixed while the rest scrolls. There's a couple ways that I think make sense, so I'll start with that.
The first way that makes sense to me is to simply break the table code format by creating a separate table as the column that we want to be by itself, something like this:
<div class="table-container">
<div class="table-column">
<table>
<thead><tr><th> </th></tr></thead>
<tbody>
<tr><td>Side Header 1</td></tr>
<tr><td>Side Header 2</td></tr>
<tr><td>Side Header 3</td></tr>
<tr><td>Side Header 4</td></tr>
<tr><td>Side Header 5</td></tr>
<tr><td>Side Header 6</td></tr>
</tbody>
</table>
</div>
<div class="table-column" style="overflow-x: auto;">
<table>
<thead>
<tr><th>Top Header 1</th><th>Top Header 2</th></tr>
</thead>
<tbody>
<tr><td>Row 1, Cell 1</td><td>Row 1, Cell 2</td></tr>
<tr><td>Row 2, Cell 1</td><td>Row 2, Cell 2</td></tr>
<tr><td>Row 3, Cell 1</td><td>Row 3, Cell 2</td></tr>
<tr><td>Row 4, Cell 1</td><td>Row 4, Cell 2</td></tr>
<tr><td>Row 5, Cell 1</td><td>Row 5, Cell 2</td></tr>
<tr><td>Row 6, Cell 1</td><td>Row 6, Cell 2</td></tr>
</tbody>
</table>
</div>
</div>
We make the first column have a <th> of just blank space so that the styling for the whole table still fits.
What I really want to do though is make this more of a dynamic process... Obviously in that case (especially using the word 'dynamic') I could just use some JS, but there must be a way to do this in CSS... but there doesn't seem to be anything solid online... so I thought I'd give it a go.
The closest I've been able to come is through using data-attribute:; and td::before, like this:
<div class="box-table">
<table class="text-center hover stripes">
<thead>
<tr>
<th data-label="Cat 1">Cat 1</th>
<th>Cat 2</th>
<th>Cat 3</th>
<th>Cat 4</th>
<th>Cat 5</th>
<th>Cat 6</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Col 1">Col 1</td>
<td>Col 2</td>
<td>Col 3</td>
<td>Col 4</td>
<td>Col 5</td>
<td>Col 6</td>
</tr>
<tr>
<td data-label="Col 1">Col 1</td>
<td>Col 2</td>
<td>Col 3</td>
<td>Col 4</td>
<td>Col 5</td>
<td>Col 6</td>
</tr>
</tbody>
</table>
</div>
<style>
tr > th:first-child,
tr > td:first-child {
padding: 0;
}
tr > th:first-child::before,
tr > td:first-child::before {
content: attr(data-label);
display: inline-block;
position: fixed;
background: #fff;
border-right: 1px solid rgba(0,0,0,0.2);
letter-spacing: 1px;
padding: 0 0.75rem;
}
<style>
Here's a fiddle with what I've gotten so far: https://jsfiddle.net/wn5nonu3/
There's 2 issues I've run into:
The first is that because I've set the item to fixed, if the overflow of the table allows vertical scrolling then the fixed will obviously stay where they are fixed and appear out of line with the row.
The second issue is that I can't seem to style td::before (it seems to be showing 'inline' behavior regardless of what I change the display:; value to?).
Potential solution to the second problem is to remove the padding causing the row's to be larger, set the first column to a fixed width and add that width to td::before. I still can't fix the first problem though.
I thought I'd share in case anyone has any ideas about how this could possibly work, or whether or not the route I'm taking is even really doable?
jsFiddle: https://jsfiddle.net/wn5nonu3/ (same as one posted above, just for easy finding)
For the record: I know there's a number of great JS options, I just like to limit the amount of scripts I throw on my pages, and this just seems like something that would be useful.

One way you could make it is by using two tables with the same style, but it's a little hard to maintain. You have to make sure both tables are on the same line and that there's no space between them. When you just wrap the table with the actual content in a div that scrolls. Honestly, I wouldn't go there unless you really don't want to use JS, but it's your call.
.table-wrapper {
width: 100%;
white-space: nowrap;
}
.table-firstcolon {
display: inline-block;
vertical-align: top;
}
.table-firstcolon td,
.table-firstcolon th {
width: 60px;
}
.table-content-wrapper {
display: inline-block;
overflow-x: scroll;
vertical-align: top;
max-width: calc(100% - 60px);
}
.table-content-wrapper>table {
display: inline-block;
vertical-align: top;
}
/* Page Setup */
*,
*::after,
*::before {
box-sizing: inherit;
}
html {
box-sizing: border-box;
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
height: 100%;
line-height: 1.5;
width: 50%;
}
body {
min-height: 100%;
padding: 5px 10px;
/* this style is only for the fiddle, would be '0' */
width: 100%;
}
/* General Table Styling */
table {
background: #fdfdfd;
border-collapse: collapse;
border-spacing: 0;
}
th {
font-weight: bold;
}
thead,
tbody,
tfoot {
border: 1px solid #f1f1f1;
}
th, td {
padding: 6px;
text-align: left;
}
thead tr:first-child {
font-weight: bold;
border-bottom: 2px solid #eee;
}
tr:nth-child(even) {
background-color: rgba(0, 0, 0, 0.025);
}
tr:hover td {
background-color: rgba(0, 0, 0, 0.04);
color: #000;
}
<div class="table-wrapper">
<table class="table-firstcolon text-center hover stripes">
<thead>
<tr>
<th> </th>
</tr>
</thead>
<tbody>
<tr>
<th>Row 1</th>
</tr>
<tr>
<th>Row 2</th>
</tr>
<tr>
<th>Row 3</th>
</tr>
<tr>
<th>Row 4</th>
</tr>
<tr>
<th>Row 5</th>
</tr>
<tr>
<th>Row 6</th>
</tr>
</tbody>
</table><!--
--><div class="table-content-wrapper">
<table>
<thead>
<tr>
<th>Column A</th>
<th>Column B</th>
<th>Column C</th>
<th>Column D</th>
<th>Column E</th>
</tr>
</thead>
<tbody>
<tr>
<td>A1</td>
<td>B1</td>
<td>C1</td>
<td>D1</td>
<td>E1</td>
</tr>
<tr>
<td>A2</td>
<td>B2</td>
<td>C2</td>
<td>D2</td>
<td>E2</td>
</tr>
<tr>
<td>A3</td>
<td>B3</td>
<td>C3</td>
<td>D3</td>
<td>E3</td>
</tr>
<tr>
<td>A4</td>
<td>B4</td>
<td>C4</td>
<td>D4</td>
<td>E4</td>
</tr>
<tr>
<td>A5</td>
<td>B5</td>
<td>C5</td>
<td>D5</td>
<td>E5</td>
</tr>
<tr>
<td>A6</td>
<td>B6</td>
<td>C6</td>
<td>D6</td>
<td>E6</td>
</tr>
</tbody>
</table>
</div>
</div>

Related

How to only make tbody vertically scrollable in a table which has dynamic column widths

I have table in the a page where I need to implement a vertical scroll only for the tbody part of the table. My table has columns of dynamic width, there's horizontal scrolling implemented if increase in width of a column causes the table to overflow. What I want is for only the body of the table to scroll on vertical overflow, but want the table header to remain visible. What I have implemented scrolls the entire table vertically
Following is my code for now. It has dummy data, as I cant post the actual code, but the structure is the same(jsfiddle link):
th,
td {
text-align: left;
padding: 5px;
outline: solid 0.5px;
}
table {
table-layout: auto;
width: 100%;
white-space: nowrap;
overflow-x: scroll;
overflow-y: scroll;
height: 100px;
display: block;
}
.container {
width: 300px;
}
<div class="container">
<table>
<thead>
<tr>
<th>Title 1</th>
<th>Name</th>
<th>Address</th>
<th>Col4</th>
<th>Col5</th>
<th>Col6</th>
</tr>
</thead>
<tbody>
<tr>
<td>Title 2</td>
<td>Jane Doe</td>
<td>dfss</td>
<td>sdffsffsfd</td>
<td>sfsfs</td>
<td>sfsff</td>
</tr>
<tr>
<td>Title 3</td>
<td>John Doe</td>
<td>sasas</td>
<td>eeeee</td>
<td>eEe</td>
<td>sfff</td>
</tr>
<tr>
<td>Title 4 is a long title</td>
<td>Name1</td>
<td>dfss</td>
<td>sdffsffsfd</td>
<td>sfsfs</td>
<td>sfsff</td>
</tr>
<tr>
<td>Title 5 is shorter</td>
<td>Name 2</td>
<td>dfsf</td>
<td>sdfsf</td>
<td>dfsf</td>
<td>sdfsf</td>
</tr>
<tr>
<td>Title 6</td>
<td>Name 3</td>
<td>sasas</td>
<td>eeeee</td>
<td>eEe</td>
<td>sfff</td>
</tr>
</tbody>
</table>
</div>
I have checked multitiple solutions on stackoverflow for this problem but they all set a fixed width for their columns and then use wrap the content inside if it exceeds the width. table with fixed thead and scrollable tbody
is the only solution that didn't completely mess up my page, but doesn't work, it gives different column widths for columns in header and body.
All other solutions, even the ones that use nested table use fixed width column, and the ones which don't use js/jQuery which I would rather not use unless its the absolute, last ever option. Can anyone please suggest something?
To make the <tbody> scrollable :
tbody{
display: block;
height: 100px;
width: 100%;
overflow-y: scroll;
}
And if you want to the <thead> to stay fixed while the body scrolls:
thead tr{
display: block
}
I'm unsure whether this is answering your question.
If the y axis is always to have a scroll and the x axis only to have
a scroll if there is too much information
CSS
overflow-x:auto;
overflow-y:scroll;
I came across this issue myself and found an alternate solution to the answer posted by #Abe Caymo
Simple non-ideal solution (by Abe)
The problem with Abe's solution is that it works fine up until you start to use thead and tfoot. Once you add these you will soon realize that the table column layout no longer syncs the column width across tbody, thead and tfoot. See demo below...
th,
td {
text-align: left;
padding: 5px;
outline: solid 0.5px;
}
table {
white-space: nowrap;
display: block;
}
tbody{
display: block;
height: 100px;
overflow-y: auto;
}
<div class="container">
<table>
<thead>
<tr>
<th>Title 1</th>
<th>Name</th>
<th>Address</th>
<th>Col4</th>
<th>Col5</th>
<th>Col6</th>
</tr>
</thead>
<tbody>
<tr>
<td>Title 2</td>
<td>Jane Doe</td>
<td>dfss</td>
<td>sdffsffsfd</td>
<td>sfsfs</td>
<td>sfsff</td>
</tr>
<tr>
<td>Title 3</td>
<td>John Doe</td>
<td>sasas</td>
<td>eeeee</td>
<td>eEe</td>
<td>sfff</td>
</tr>
<tr>
<td>Title 4 is a long title</td>
<td>Name1</td>
<td>dfss</td>
<td>sdffsffsfd</td>
<td>sfsfs</td>
<td>sfsff</td>
</tr>
<tr>
<td>Title 5 is shorter</td>
<td>Name 2</td>
<td>dfsf</td>
<td>sdfsf</td>
<td>dfsf</td>
<td>sdfsf</td>
</tr>
<tr>
<td>Title 6</td>
<td>Name 3</td>
<td>sasas</td>
<td>eeeee</td>
<td>eEe</td>
<td>sfff</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>Title 1</th>
<th>Name</th>
<th>Address</th>
<th>Col4</th>
<th>Col5</th>
<th>Col6</th>
</tr>
</tfoot>
</table>
</div>
Slightly more ideal solution
A better solution which maintains the auto table-layout is to set the thead and tfoot to position: sticky.
A few caveats and things to understand about this approach.
The overflow or element actually scrolling, is the div container of the table. You must have this and this is what you may use to control the size of the table. As such, the scroll bar will always be the full height of the scrollable table.
The background-color must be set to an opaque value otherwise the rows in the tbody will show behind the header as it passes below when scrolling.
The borders/outlines are much harder to get right but with a little finessing you can find a compatible style. Adding a border or outline to either thead or tfoot will not be sticky.
.container {
height: 140px;
min-height: 100px;
overflow: auto;
resize: vertical; /* only for demo */
}
thead,
tfoot {
/* must background-color otherwise transparent will show rows underneath */
background-color: white;
position: sticky;
}
thead {
margin-bottom: 0;
top: 0;
}
tfoot {
margin-top: 0;
bottom: 0;
}
th,
td {
text-align: left;
padding: 5px;
outline: solid black 0.5px;
}
table {
width: 100%;
}
<div class="container">
<table>
<thead>
<tr>
<th>Title 1</th>
<th>Name</th>
<th>Address</th>
<th>Col4</th>
<th>Col5</th>
<th>Col6</th>
</tr>
</thead>
<tbody>
<tr>
<td>Title 2</td>
<td>Jane Doe</td>
<td>dfss</td>
<td>sdffsffsfd</td>
<td>sfsfs</td>
<td>sfsff</td>
</tr>
<tr>
<td>Title 3</td>
<td>John Doe</td>
<td>sasas</td>
<td>eeeee</td>
<td>eEe</td>
<td>sfff</td>
</tr>
<tr>
<td>Title 4 is a long title</td>
<td>Name1</td>
<td>dfss</td>
<td>sdffsffsfd</td>
<td>sfsfs</td>
<td>sfsff</td>
</tr>
<tr>
<td>Title 5 is shorter</td>
<td>Name 2</td>
<td>dfsf</td>
<td>sdfsf</td>
<td>dfsf</td>
<td>sdfsf</td>
</tr>
<tr>
<td>Title 6</td>
<td>Name 3</td>
<td>sasas</td>
<td>eeeee</td>
<td>eEe</td>
<td>sfff</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>Title 1</th>
<th>Name</th>
<th>Address</th>
<th>Col4</th>
<th>Col5</th>
<th>Col6</th>
</tr>
</tfoot>
</table>
</div>
The final result will look something like that below with all columns aligned respectively...
Also see this solution using display: grid on the table element.

How to make a column auto width in fixed layout table in 2018

I believe the width: 1px; white-space: nowrap; trick worked before but it seems not anymore now? Ref. CSS table column autowidth (there the table was also in fixed layout but thats back in 2011)
Here is the HTML and CSS:
table {
table-layout: fixed;
width: 100%;
}
td,
th {
border: 1px solid #D5D5D5;
padding: 15px;
}
.auto {
width: 1px;
white-space: nowrap;
}
<table cellspacing="0" cellpadding="0" border="0">
<thead>
<tr>
<th>Column 1 even width</th>
<th>Column 2 even width</th>
<th>Column 3 even width</th>
<th class="auto">Auto</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data 1</td>
<td>Data 2</td>
<td>Data 3</td>
<td class="auto">Data4</td>
</tr>
</tbody>
</table>
I also have an example setup in the following jsfiddle:
http://jsfiddle.net/hCkch/21/
How do you make the last column auto width based on the content while the other columns respect table-layout: fixed?
Note: the three columns given above is just an example. So please no hardcoded answers to make each column width 100/3%. This is a general question and the answer should fit for n columns with even width but the last one auto width based on the content.
With table-layout, you will want to set the widths of the cells in the first row of the table (https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout). You'll also want to add a width of 100% for the final column that is meant to auto-expand. Here is an edit of your jsfiddle:
http://jsfiddle.net/jessbodie/hCkch/31/
<table cellspacing="0" cellpadding="0" border="0">
<thead>
<tr>
<th class="col1st">Column 1 even width</th>
<th class="col2nd">Column 2 even width</th>
<th class="col3rd">Column 3 even width</th>
<th class="auto">Auto</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data 1</td>
<td>Data 2</td>
<td>Data 3</td>
<td class="auto">Data4</td>
</tr>
</tbody>
</table>
table {
table-layout: fixed;
width: 100%;
}
td, th {
border: 1px solid red;
padding: 15px;
}
.col1st {
width: 100px;
}
.col2nd {
width: 100px;
}
.col3rd {
width: 100px;
}
.auto {
width: 100%;
white-space: nowrap;
}
For making the first n columns the same width, in SASS you can use the calc function to come up with the widths.
As mentioned in the comments, the only way I know to do this that should work cross browser is to get rid of table-layout: fixed; and set the width of the remaining columns. Fixed layouts are good for lots of things but automatically calculating based on content doesn't seem to be one of them.
table {
width: 100%;
}
td, th {
border: 1px solid #D5D5D5;
padding: 15px;
width: 33.33%
}
.auto {
white-space: nowrap;
}
<table cellspacing="0" cellpadding="0" border="0">
<thead>
<tr>
<th>Column 1 even width</th>
<th>Column 2</th>
<th>Column 3</th>
<th class="auto">Auto</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data 1</td>
<td>Data 2</td>
<td>Data 3</td>
<td>Data4</td>
</tr>
</tbody>
</table>
I realize you clarified that you don't want markup specific rules, but it does in fact accomplish your goal with the one requirement that you know how many columns you will be dealing with beforehand. In general, this should not be prohibitive.
Another possibly work around is to assign the widths client side using js after the fact:
$(document).ready(function() {
var width = 100 / $('table tr:first th:not(.auto)').length;
var cols = $('table th:not(.auto)');
cols.css('width', width + '%');
});
table {
width: 100%;
}
td, th {
border: 1px solid #D5D5D5;
padding: 15px;
}
.auto {
white-space: nowrap;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table cellspacing="0" cellpadding="0" border="0">
<thead>
<tr>
<th>Column 1 even width</th>
<th>Column 2</th>
<th>Column 3</th>
<th class="auto">Auto</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data 1</td>
<td>Data 2</td>
<td>Data 3</td>
<td>Data4</td>
</tr>
</tbody>
</table>
And finally, I'll mention there is a Firefox specific implementation of min-content that actually does exactly what you want. Unfortunately, it only works on Firefox:
table {
width: 100%;
table-layout: fixed;
}
td, th {
border: 1px solid #D5D5D5;
padding: 15px;
}
.auto {
width: -moz-min-content;
white-space: nowrap;
}
<table cellspacing="0" cellpadding="0" border="0">
<thead>
<tr>
<th>Column 1 even width</th>
<th>Column 2</th>
<th>Column 3</th>
<th class="auto">Auto</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data 1</td>
<td>Data 2</td>
<td>Data 3</td>
<td>Data4</td>
</tr>
</tbody>
</table>

How to avoid page break between <thead> and <tbody>

I'm trying to make a printable document with some quite long tables. And sometimes page ends right between table headers and the data, which makes it harder to read.
Example
How could I prevent that?
I've tried to use the following CSS but it didn't help.
#media print {
h1, h2, h3, thead, thead>tr {
page-break-after: avoid;
page-break-inside: avoid;
}
tbody {
page-break-before: avoid;
}
/* I would also like to not have page breaks within first five rows */
tbody:nth-child(-n+5) {
page-break-before: avoid;
}
}
Table structure:
<table border="1" cellspacing="0" cellpadding="3">
<thead>
<tr>
<th rowspan="2">Metric</th>
<th colspan="3">Type 1</th>
<th colspan="3">Type 2<th>
</tr>
<tr>
<th>Initial</th>
<th>Final</th>
<th>Difference</th>
<th>Initial</th>
<th>Final</th>
<th>Difference</th>
</tr>
</thead>
<tbody>
<tr>
<td>Dataset1</td>
<td>*DATA*</td>
...
</tr>
</tbody>
</table>
I found a workaround for this issue, based on the solution suggested here: How do I avoid a page break immediately after a heading
Add a wrapper to your table and add a before pseudo-element to it. This element won't actually take up any space (due to the negative margin-bottom), but its height will be used when calculating where to put the page break, forcing the browser to push the table to the next page if it's too close to the bottom.
200px should be replaced with a value that is slightly more than the height of the header + the height of the first row of the body.
/* This is the solution */
.wrapper::before {
content: "";
display: block;
height: 200px;
margin-bottom: -200px;
page-break-inside: avoid;
break-inside: avoid;
}
/* Table styles */
table {
width: 100%;
}
thead {
background: green;
}
thead tr {
page-break-inside: avoid;
break-inside: avoid;
}
tbody {
background: yellow;
}
tbody tr {
height: 80px;
}
td {
height: 80px;
}
<div class="wrapper">
<table>
<thead>
<tr>
<td>header</td>
<td>header</td>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
</tr>
</tbody>
</table>
</div>
Apply page-break to a block-level pseudo-element on your tbody instead of directly applying it to tbody.
Here is working Demo
You must carefully define your page-context and appropriate margins and dimensions to suit your use-case.
table, th, td { border: 1px solid gray; border-collapse: collapse; }
th, td { padding: 8px; }
tbody:first-of-type { background-color: #eee; }
#page {
size: A4;
margin: 0;
}
#media print {
html, body {
width: 210mm;
height: 297mm;
}
tbody::after {
content: ''; display: block;
page-break-after: always;
page-break-inside: avoid;
page-break-before: avoid;
}
}
<div>
Print
</div>
<hr />
<table>
<thead>
<tr>
<th>Head 1</th>
<th>Head 2</th>
<th>Head 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Cell 1</td>
<td>Row 1 Cell 2</td>
<td>Row 1 Cell 3</td>
</tr>
<tr>
<td>Row 2 Cell 1</td>
<td>Row 2 Cell 2</td>
<td>Row 2 Cell 3</td>
</tr>
</tbody>
<tbody>
<tr>
<td>Row 3 Cell 1</td>
<td>Row 3 Cell 2</td>
<td>Row 3 Cell 3</td>
</tr>
<tr>
<td>Row 4 Cell 1</td>
<td>Row 4 Cell 2</td>
<td>Row 4 Cell 3</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>Foot 1</th>
<th>Foot 2</th>
<th>Foot 3</th>
</tr>
</tfoot>
</table>

HTML table with horizontal scrolling (first column fixed)

I have been trying to think of a way to make a table with a fixed first column (and the rest of the table with a horizontal overflow) I saw a post which had a similar question. but the fixed column bit did not seem to be resolved. Help?
How about:
table {
table-layout: fixed;
width: 100%;
*margin-left: -100px; /*ie7*/
}
td, th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 10px;
width: 100px;
}
.fix {
position: absolute;
*position: relative; /*ie7*/
margin-left: -100px;
width: 100px;
}
.outer {
position: relative;
}
.inner {
overflow-x: scroll;
overflow-y: visible;
width: 400px;
margin-left: 100px;
}
<div class="outer">
<div class="inner">
<table>
<tr>
<th class=fix></th>
<th>Col 1</th>
<th>Col 2</th>
<th>Col 3</th>
<th>Col 4</th>
<th class="fix">Col 5</th>
</tr>
<tr>
<th class=fix>Header A</th>
<td>col 1 - A</td>
<td>col 2 - A (WITH LONGER CONTENT)</td>
<td>col 3 - A</td>
<td>col 4 - A</td>
<td class=fix>col 5 - A</td>
</tr>
<tr>
<th class=fix>Header B</th>
<td>col 1 - B</td>
<td>col 2 - B</td>
<td>col 3 - B</td>
<td>col 4 - B</td>
<td class=fix>col 5 - B</td>
</tr>
<tr>
<th class=fix>Header C</th>
<td>col 1 - C</td>
<td>col 2 - C</td>
<td>col 3 - C</td>
<td>col 4 - C</td>
<td class=fix>col 5 - C</td>
</tr>
</table>
</div>
</div>
You can test it out in this jsbin: http://jsbin.com/uxecel/4/edit
Based on skube's approach, I found the minimal set of CSS I needed was:
.horizontal-scroll-except-first-column {
width: 100%;
overflow: auto;
}
.horizontal-scroll-except-first-column > table {
margin-left: 8em;
}
.horizontal-scroll-except-first-column > table > * > tr > th:first-child,
.horizontal-scroll-except-first-column > table > * > tr > td:first-child {
position: absolute;
width: 8em;
margin-left: -8em;
background: #ccc;
}
.horizontal-scroll-except-first-column > table > * > tr > th,
.horizontal-scroll-except-first-column > table > * > tr > td {
/* Without this, if a cell wraps onto two lines, the first column
* will look bad, and may need padding. */
white-space: nowrap;
}
<div class="horizontal-scroll-except-first-column">
<table>
<tbody>
<tr>
<td>FIXED</td> <td>22222</td> <td>33333</td> <td>44444</td> <td>55555</td> <td>66666</td> <td>77777</td> <td>88888</td> <td>99999</td> <td>AAAAA</td> <td>BBBBB</td> <td>CCCCC</td> <td>DDDDD</td> <td>EEEEE</td> <td>FFFFF</td>
</tr>
</tbody>
</table>
</div>
I have a similar table styled like so:
<table style="width:100%; table-layout:fixed">
<tr>
<td style="width: 150px">Hello, World!</td>
<td>
<div>
<pre style="margin:0; overflow:scroll">My preformatted content</pre>
</div>
</td>
</tr>
</table>
Use jQuery DataTables plug-in, it supports fixed header and columns.
This example adds fixed column support to the html table "example":
http://datatables.net/extensions/fixedcolumns/
For two fixed columns:
http://www.datatables.net/release-datatables/extensions/FixedColumns/examples/two_columns.html
You can use below table style to have horizontal scrolling table with fixed first column.
<style type="text/css">
table, th, td {
border: 1px solid black;
}
.table-style {
overflow-x: auto;
}
.table-style tr th:first-child {
position: sticky;
left: 0;
z-index: 2;
background-color: white;
}
</style>
<div class="table-style">
<table>
<thead>
<tr>
<th>_col1_row1_</th>
<th>_col2_row1_</th>
<th>_col3_row1_</th>
<th>_col4_row1_</th>
<th>_col5_row1_</th>
</tr>
</thead>
<tbody>
<tr>
<th>_col1_row2_</th>
<td>_col2_row2_</td>
<td>_col3_row2_</td>
<td>_col4_row2_</td>
<td>_col5_row2_</td>
</tr>
<tr>
<th>_col1_row3_</th>
<td>_col2_row3_</td>
<td>_col3_row3_</td>
<td>_col4_row3_</td>
<td>_col5_row3_</td>
</tr>
<tr>
<th>_col1_row4_</th>
<td>_col2_row4_</td>
<td>_col3_row4_</td>
<td>_col4_row4_</td>
<td>_col5_row4_</td>
</tr>
<tr>
<th>_col1_row5_</th>
<td>_col2_row5_</td>
<td>_col3_row5_</td>
<td>_col4_row5_</td>
<td>_col5_row5_</td>
</tr>
</tbody>
</table>
Take a look at this JQuery plugin:
http://fixedheadertable.com
It adds vertical (fixed header row) or horizontal (fixed first column) scrolling to an existing HTML table. There is a demo you can check for both cases of scrolling.
Here's an example with fixed first and last columns. Sticky works better than absolute positioning for expanding the rows when the cells have more content.
Note - this also works with rowspan table cells. The table holds its correct shape.
.wrapper {
overflow-x:scroll;
width:100%;
}
table {
table-layout: fixed;
width: 100%;
border-collapse: collapse;
background: white;
}
tr {
border-top: 1px solid #ccc;
}
td, th {
vertical-align: top;
text-align: left;
width:150px;
padding: 5px;
}
.fix {
position:sticky;
background: white;
}
.fix:first-child {
left:0;
width:180px;
}
.fix:last-child {
right:0;
width:120px;
}
<div class="wrapper">
<table>
<thead>
<th class='fix'>Fixed</th>
<th>Col 1</th>
<th>Col 2</th>
<th>Col 3</th>
<th>Col 4</th>
<th>Col 5</th>
<th class='fix'>Fixed</th>
</thead>
<tbody>
<tr>
<td class='fix'>First Content</td>
<td>A1</td>
<td>A2 (with longer content)</td>
<td>A3</td>
<td>A4</td>
<td>A5</td>
<td class='fix'>Last Content</td>
</tr>
<tr>
<td class='fix'>First Content (with longer content)</td>
<td>B1</td>
<td>B2</td>
<td>B3</td>
<td>B4</td>
<td>B5</td>
<td class='fix'>Last Content</td>
</tr>
<tr>
<td class='fix'>First Content</td>
<td>C1</td>
<td>C2</td>
<td>C3</td>
<td>C4</td>
<td>C5</td>
<td class='fix'>Last Content (with longer content)</td>
</tr>
</tbody>
</table>
</div>
Example to play with: https://jsbin.com/qawidijigi/edit?html,css,output

How to put spacing between TBODY elements

I have a table like this:
<table>
<tfoot>
<tr><td>footer</td></tr>
</tfoot>
<tbody>
<tr><td>Body 1</td></tr>
<tr><td>Body 1</td></tr>
<tr><td>Body 1</td></tr>
</tbody>
<tbody>
<tr><td>Body 2</td></tr>
<tr><td>Body 2</td></tr>
<tr><td>Body 2</td></tr>
</tbody>
<tbody>
<tr><td>Body 3</td></tr>
<tr><td>Body 3</td></tr>
<tr><td>Body 3</td></tr>
</tbody>
</table>
I'd like to put some spacing between each tbody element, but padding and margin have no effect. Any ideas?
Something like this will work, depending on your browser support requirements:
tbody::before
{
content: '';
display: block;
height: 15px;
}
Try this, if you don't mind not having borders.
<style>
table {
border-collapse: collapse;
}
table tbody {
border-top: 15px solid white;
}
</style>
<table>
<tfoot>
<tr><td>footer</td></tr>
</tfoot>
<tbody>
<tr><td>Body 1</td></tr>
<tr><td>Body 1</td></tr>
<tr><td>Body 1</td></tr>
</tbody>
<tbody>
<tr><td>Body 2</td></tr>
<tr><td>Body 2</td></tr>
<tr><td>Body 2</td></tr>
</tbody>
<tbody>
<tr><td>Body 3</td></tr>
<tr><td>Body 3</td></tr>
<tr><td>Body 3</td></tr>
</tbody>
</table>
People will always have controversial opinions about using empty table elements to layout a page (as evidenced by this answer's downvote). I recognize this, but sometimes its easier to use them this way when you are working in a "quick and dirty" way.
I've used empty rows in past projects to space groups of table rows. I assigned the spacer rows a css class of their own and defined a height for that class that acted as a top and bottom margin for that group of table rows.
.separator{
height: 50px;
}
<table>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr class="separator" colspan="2"></tr>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr class="separator" colspan="2"></tr>
tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
</table>
If you don't have borders on your table cells, you could also define a height to your typical cell or row in your style sheet that evenly spaces out all rows of your table.
tr{
height: 40px;
}
I had been having trouble with cross-browser support for spacing multiple <tbody>'s using the ::before pseudo-selector if any <td>'s had a rowspan.
Basically, if your <tbody> is structured like this:
<tbody>
<tr>
<td>td 1</td>
<td rowspan"2">td 2</td>
<td>td 3</td>
<td>td 4</td>
</tr>
<tr>
<td>td 1</td>
<td>td 2</td>
<td>td 4</td>
</tr>
</tbody>
...and your ::before pseudo-selector displays the content as a block like this:
tbody::before
{
content: '';
display: block;
height: 10px;
}
...then this will cause the table to cut off any columns that use rowspan.
The solution is to style ::before pseudo as a table-row:
tbody::before
{
content: '';
display: table-row;
height: 10px;
}
This should have good cross-browser support.
Here's a fiddle
Here's another possibility that relies on :first-child which is not available in all browsers:
<style>
table {
border-collapse: collapse;
}
td {
border: 1px solid black;
}
tbody tr:first-child td {
padding-top: 15px;
}
</style>
<table>
<tfoot>
<tr><td>footer</td></tr>
</tfoot>
<tbody>
<tr><td>Body 1</td></tr>
<tr><td>Body 1</td></tr>
<tr><td>Body 1</td></tr>
</tbody>
<tbody>
<tr><td>Body 2</td></tr>
<tr><td>Body 2</td></tr>
<tr><td>Body 2</td></tr>
</tbody>
<tbody>
<tr><td>Body 3</td></tr>
<tr><td>Body 3</td></tr>
<tr><td>Body 3</td></tr>
</tbody>
</table>
Just set display as block and it will work.
table tbody{
display:block;
margin-bottom:10px;
border-radius: 5px;
}
Of all of the answers given above, only djenson47's answers retain separation of presentation and content. The drawback of the collapsed border model method is that you can no longer use the table's border or cellspacing attributes to separate the individual cells. You could argue that this is a good thing, and there are some workarounds, but it can be a pain. So I think the first-child method is the most elegant.
Alternatively, you could also set your TBODY class' overflow property to anything other than "visible." This method allows you to retain a separated borders model as well:
<style>
tbody {
overflow: auto;
border-top: 1px solid transparent;
}
</style>
<table>
<tfoot>
<tr><td>footer</td></tr>
</tfoot>
<tbody>
<tr><td>Body 1</td></tr>
<tr><td>Body 1</td></tr>
<tr><td>Body 1</td></tr>
</tbody>
<tbody>
<tr><td>Body 2</td></tr>
<tr><td>Body 2</td></tr>
<tr><td>Body 2</td></tr>
</tbody>
<tbody>
<tr><td>Body 3</td></tr>
<tr><td>Body 3</td></tr>
<tr><td>Body 3</td></tr>
</tbody>
</table>
You can use border-spacing in a table with table row groups to add a space between those groups. Though, I don't think there is a way to specify which groups are spaced and which are not.
<table>
<thead>
...
</head>
<tbody>
...
</tbody>
<tbody>
...
</tbody>
<tfoot>
...
</tfoot>
</table>
CSS
table {
border-spacing: 0px 10px; /* h-spacing v-spacing */
}
Because padding can be applied to TD's, you can do a trick with the + sign. Then it will be possible to give a top padding to the TD's of the first TR of a tbody:
// The first row will have a top padding
table tbody + tbody tr td {
padding-top: 20px;
}
// The rest of the rows should not have a padding
table tbody + tbody tr + tr td {
padding-top: 0px;
}
I have added the "tbody + tbody" so the first tbody won't have a top padding. However, it's not required.
As far as I know there are no drawbacks :), though didn't test the older browsers.
NEW ANSWER
You can use as many <tbody> tags as you like. I didn't realize that was ok by W3C until now. Not to say my below solution doesn't work (it does), but to do what you're trying to do, assign your <tbody> tags classes and then reference their individual <td> tags through CSS like so:
table tbody.yourClass td {
padding: 10px;
}
and your HTML thusly:
<table>
<tbody>
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
</tbody>
<tbody class="yourClass">
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
</tbody>
<tbody>
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
</tbody>
</table>
Try that guy out :)
OLD ANSWER
whatever you do, DON'T insert blank rows...
you shouldn't have more than 1 tbody element in your table. what you can do is set the class or id attribute in your <tr> elements and give their corresponding <td> tags padding:
table {
border-collapse: collapse;
}
tr.yourClass td {
padding: 10px;
}
You can even assign the top and bottom <tr>'s an additional class so that they only do top or bottom padding, respectively:
tr.yourClass.topClass td {
padding: 10px 0 0 0;
}
tr.yourClass.bottomClass td {
padding: 0 0 10px 0;
}
and in your HTML, your <tr> tag would look like this:
<table>
<tbody>
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
<tr class="yourClass topClass"><td>Text</td></tr>
<tr class="yourClass"><td>Text</td></tr>
<tr class="yourClass bottomClass"><td>Text</td></tr>
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
<tr><td>Text</td></tr>
</tbody>
</table>
Hope this helps!
djensen47 answer works great for newer browsers, however, as it was pointed out, IE7 it does not work in.
My workaround for this issue to support the older browsers was to wrap each cells contents inside a div. Then add a margin-top to the div.
<table class="tbl">
<tr></tr>
<tr></tr>
<tr></tr>
<tr><td><div></div></td></tr>
</table>
CSS
.tbl tr td div {
height:30px;
margin-top:20px;
}
The height setting keeps the cells at least 30px high to prevent any cell coloring used inside the div from collapsing around the text. The margin-top creates the desired space by making the entire row taller.
Came across this while trying to solve it myself. I had success with putting a <br> tag right before the closing </tbody> tag. It is a purely visual fix, but seems to work on most browsers I tested.
<table>
<tbody>
<tr>
<td></td>
</tr>
<br>
</tbody>
<tbody>
<tr>
<td></td>
</tr>
</tbody>
</table>
Should be accessible as well.
With credit to everyone else who answered first ...
table {
border-collapse: collapse;
table-layout: fixed;
width: 50%;
}
tbody:before {
content: "";
display:block;
border-top: 15px solid white;
}
tbody tr {
border-color: #000;
border-style: solid;
}
tbody tr:first-of-type{
border-width: 2px 2px 0 2px;
}
tbody tr:nth-of-type(1n+2){
border-width: 0 2px 0 2px;
}
tbody tr:last-of-type{
border-width: 0 2px 2px 2px;
}
tbody tr:nth-child(odd) {
background-color: #ccc;
}
tbody tr:hover {
background-color: #eee;
}
td {
text-align: right;
}
<table>
<tbody>
<tr>
<th colspan="3">One</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
<tbody>
<tr>
<th colspan="3">Two</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
</table>