Table fixed header and scrollable body
Solution 1
Here is the working solution:
table {
width: 100%;
}
thead, tbody, tr, td, th { display: block; }
tr:after {
content: ' ';
display: block;
visibility: hidden;
clear: both;
}
thead th {
height: 30px;
/*text-align: left;*/
}
tbody {
height: 120px;
overflow-y: auto;
}
thead {
/* fallback */
}
tbody td, thead th {
width: 19.2%;
float: left;
}
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"/>
<table class="table table-striped">
<thead>
<tr>
<th>Make</th>
<th>Model</th>
<th>Color</th>
<th>Year</th>
</tr>
</thead>
<tbody>
<tr>
<td class="filterable-cell">Ford</td>
<td class="filterable-cell">Escort</td>
<td class="filterable-cell">Blue</td>
<td class="filterable-cell">2000</td>
</tr>
<tr>
<td class="filterable-cell">Ford</td>
<td class="filterable-cell">Escort</td>
<td class="filterable-cell">Blue</td>
<td class="filterable-cell">2000</td>
</tr>
<tr>
<td class="filterable-cell">Ford</td>
<td class="filterable-cell">Escort</td>
<td class="filterable-cell">Blue</td>
<td class="filterable-cell">2000</td>
</tr>
<tr>
<td class="filterable-cell">Ford</td>
<td class="filterable-cell">Escort</td>
<td class="filterable-cell">Blue</td>
<td class="filterable-cell">2000</td>
</tr>
</tbody>
</table>
Link to jsfiddle
Solution 2
Fixed table head - CSS-only
Simply position: sticky; top: 0;
your th
elements. (Chrome, FF, Edge)
.tableFixHead { overflow: auto; height: 100px; }
.tableFixHead thead th { position: sticky; top: 0; z-index: 1; }
/* Just common table stuff. Really. */
table { border-collapse: collapse; width: 100%; }
th, td { padding: 8px 16px; }
th { background:#eee; }
<div class="tableFixHead">
<table>
<thead>
<tr><th>TH 1</th><th>TH 2</th></tr>
</thead>
<tbody>
<tr><td>A1</td><td>A2</td></tr>
<tr><td>B1</td><td>B2</td></tr>
<tr><td>C1</td><td>C2</td></tr>
<tr><td>D1</td><td>D2</td></tr>
<tr><td>E1</td><td>E2</td></tr>
</tbody>
</table>
</div>
For both sticky vertical TH and horizontal TH columns (inside TBODY
):
.tableFixHead { overflow: auto; height: 100px; width: 240px; }
.tableFixHead thead th { position: sticky; top: 0; z-index: 1; }
.tableFixHead tbody th { position: sticky; left: 0; }
.tableFixHead { overflow: auto; height: 100px; width: 240px; }
.tableFixHead thead th { position: sticky; top: 0; z-index: 1; }
.tableFixHead tbody th { position: sticky; left: 0; }
/* Just common table stuff. Really. */
table { border-collapse: collapse; width: 100%; }
th, td { padding: 8px 16px; white-space: nowrap; }
th { background:#eee; }
<div class="tableFixHead">
<table>
<thead>
<tr><th></th><th>TH 1</th><th>TH 2</th></tr>
</thead>
<tbody>
<tr><th>Foo</th><td>Some long text lorem ipsum</td><td>Dolor sit amet</td></tr>
<tr><th>Bar</th><td>B1</td><td>B2</td></tr>
<tr><th>Baz</th><td>C1</td><td>C2</td></tr>
<tr><th>Fuz</th><td>D1</td><td>D2</td></tr>
<tr><th>Zup</th><td>E1</td><td>E2</td></tr>
</tbody>
</table>
</div>
TH borders problem fix
Since border
cannot be painted properly on a translated TH
element,
to recreate and render "borders" use the box-shadow
property:
/* Borders (if you need them) */
.tableFixHead,
.tableFixHead td {
box-shadow: inset 1px -1px #000;
}
.tableFixHead th {
box-shadow: inset 1px 1px #000, 0 1px #000;
}
.tableFixHead { overflow: auto; height: 100px; }
.tableFixHead thead th { position: sticky; top: 0; z-index: 1; }
/* Just common table stuff. Really. */
table { border-collapse: collapse; width: 100%; }
th, td { padding: 8px 16px; }
th { background:#eee; }
/* Borders (if you need them) */
.tableFixHead,
.tableFixHead td {
box-shadow: inset 1px -1px #000;
}
.tableFixHead th {
box-shadow: inset 1px 1px #000, 0 1px #000;
}
<div class="tableFixHead">
<table>
<thead>
<tr><th>TH 1</th><th>TH 2</th></tr>
</thead>
<tbody>
<tr><td>A1</td><td>A2</td></tr>
<tr><td>B1</td><td>B2</td></tr>
<tr><td>C1</td><td>C2</td></tr>
<tr><td>D1</td><td>D2</td></tr>
<tr><td>E1</td><td>E2</td></tr>
</tbody>
</table>
</div>
Fixed table head - using JS. (IE)
You can use a bit of JS and translateY the th
elements
jQuery example
var $th = $('.tableFixHead').find('thead th')
$('.tableFixHead').on('scroll', function() {
$th.css('transform', 'translateY('+ this.scrollTop +'px)');
});
.tableFixHead { overflow-y: auto; height: 100px; }
/* Just common table stuff. */
table { border-collapse: collapse; width: 100%; }
th, td { padding: 8px 16px; }
th { background:#eee; }
<div class="tableFixHead">
<table>
<thead>
<tr><th>TH 1</th><th>TH 2</th></tr>
</thead>
<tbody>
<tr><td>A1</td><td>A2</td></tr>
<tr><td>B1</td><td>B2</td></tr>
<tr><td>C1</td><td>C2</td></tr>
<tr><td>D1</td><td>D2</td></tr>
<tr><td>E1</td><td>E2</td></tr>
</tbody>
</table>
</div>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
Or plain ES6 if you prefer (no jQuery required):
// Fix table head
function tableFixHead (e) {
const el = e.target,
sT = el.scrollTop;
el.querySelectorAll("thead th").forEach(th =>
th.style.transform = `translateY(${sT}px)`
);
}
document.querySelectorAll(".tableFixHead").forEach(el =>
el.addEventListener("scroll", tableFixHead)
);
Solution 3
Likely you'll get multiple tables on one page, therefore you need CSS classes. Please find a modified @giulio's solution for that.
Just declare it in table:
<table class="table table-striped header-fixed"></table>
CSS
.header-fixed {
width: 100%
}
.header-fixed > thead,
.header-fixed > tbody,
.header-fixed > thead > tr,
.header-fixed > tbody > tr,
.header-fixed > thead > tr > th,
.header-fixed > tbody > tr > td {
display: block;
}
.header-fixed > tbody > tr:after,
.header-fixed > thead > tr:after {
content: ' ';
display: block;
visibility: hidden;
clear: both;
}
.header-fixed > tbody {
overflow-y: auto;
height: 150px;
}
.header-fixed > tbody > tr > td,
.header-fixed > thead > tr > th {
width: 20%;
float: left;
}
Be aware that current implementation suits five columns only. If you need a different number, change the width parameter from 20% to *100% / number_of_columns*.
Solution 4
Update
For newer and still maintained library try jquery.floatThead (as mentioned by Bob Jordan in the comment) instead.
Old Answer
This is a very old answer, the library mentioned below no longer maintained.
I am using StickyTableHeaders on GitHub and it works like charm!
I had to add this css to make the header not transparent though.
table#stickyHeader thead {
border-top: none;
border-bottom: none;
background-color: #FFF;
}
Solution 5
Don't need the wrap it in a div...
CSS:
tr {
width: 100%;
display: inline-table;
table-layout: fixed;
}
table{
height:300px; // <-- Select the height of the table
display: block;
}
tbody{
overflow-y: scroll;
height: 200px; // <-- Select the height of the body
width: 100%;
position: absolute;
}
Bootply : https://www.codeply.com/p/nkaQFc713a
Comments
-
giulio almost 2 years
I am trying to make a table with fixed header and a scrollable content using the bootstrap 3 table. Unfortunately the solutions I have found does not work with bootstrap or mess up the style.
Here there is a simple bootstrap table, but for some reason to me unknown the height of the tbody is not 10px.
height: 10px !important; overflow: scroll;
Example:
<link rel="stylesheet" type="text/css" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css"> <table class="table table-striped"> <thead> <tr> <th>Make</th> <th>Model</th> <th>Color</th> <th>Year</th> </tr> </thead> <tbody style="height: 10px !important; overflow: scroll; "> <tr> <td class="filterable-cell">111 Ford</td> <td class="filterable-cell">Escort</td> <td class="filterable-cell">Blue</td> <td class="filterable-cell">2000</td> </tr> <tr> <td class="filterable-cell">Ford</td> <td class="filterable-cell">Escort</td> <td class="filterable-cell">Blue</td> <td class="filterable-cell">2000</td> </tr> <tr> <td class="filterable-cell">Ford</td> <td class="filterable-cell">Escort</td> <td class="filterable-cell">Blue</td> <td class="filterable-cell">2000</td> </tr> <tr> <td class="filterable-cell">Ford</td> <td class="filterable-cell">Escort</td> <td class="filterable-cell">Blue</td> <td class="filterable-cell">2000</td> </tr> </tbody> </table>
-
giulio over 10 yearsdisplay:block was necessary but then I also had to put float:left and a proper width for all the cell. The solution I have posted is now working.
-
T.J. Crowder almost 10 yearsDoesn't work in IE9 (and probably others); requires fixed-width columns (and there are much simpler, more cross-browser solutions if you're going to use fixed-width columns); breaks badly if the content exceeds the fixed width; doesn't actually line up the headers and the columns (you can see it in the fiddle linked above; it's clearer in this version that gives them a simple border) although that could probably be fixed; the scrollbar isn't quite lined up with the body of the table...
-
Apolo over 9 yearsadd a visible border to td and th and you will see that td's width does not match th's width
-
Tmac about 9 yearsThis looks great until you realize that the column data has to be the same width. If not then data doesn't fit under the headings.
-
Chetan about 9 yearsafter following all the different complex solutions, I find that this is the best solution. However the vertical scroll bar at has some width(which is obvious), but due to it there is the slight mismatch alignment between headers and the rows. I have 7 columns, so I have set width to 14.28% after calculating using the mention formula
-
user1807271 almost 9 yearsHow would I assign a certain width to each column?
-
Luke over 8 years@user1807271 you can assign each column's width via JS, or create a per-column class (say "col1", "col2" and so on) with the width you need and assign class "col1" to all the cells on the first column, "col2" con the second one, etc..
-
Rob Sedgwick over 8 yearsNote that the stickyTableHeaders plugin will only find html that is in the page when browser loaded it initially, it won't pick up dynamically generated content
-
Rosdi Kasim over 8 years@RobSedgwick, I haven't this this, but it should still work. As long as you initialize stickyTableHeadres AFTER the table is generated. Meaning you can't initialize it in the head but instead initialize it right after the dynamically generated table is completed.
-
Jeffrey A. Gochin over 8 yearsThe problem with this solution is that it requires a fixed height. That is not going to work where responsive design come in. I have been working on a solution that uses position: fixed, which solves the scrolling issue, but messes up the row widths.
-
Omar Wagih about 8 yearsI suggest you add
.header-fixed > thead > tr > th{white-space: nowrap;}
as well. If headers start wrapping it messes things up -
Matt Inamdar about 8 yearsFantastic solution. Much appreciated. If anyone is having problems making the background opaque, I had to use
.tableFloatingHeaderOriginal { //css }
. -
John Zabroski almost 8 yearsThis solution doesn't work when individual cells overflow.
-
Ben Hoffman almost 8 yearsYou can manually fix the td width not matching the th width by seperating out the width % declaration for the td & th and just set the th to be slightly more narrow. You will have do it custom every time but this isn't something that should be used that often.
-
Dejell over 7 yearswhat if I don't want to have a scroll?
-
Dmitry over 7 years
tbody
after settingwidth: 100%
filled page width. Was fixed byposition: relative
totable
-
Mati over 7 yearsThis plugin did not work for me, as occasionally the head floats out of the table and then floats back, which makes it look like st is broken.
-
kevinc about 7 yearsthis does not work in chrome, either the bootply or the solution.
-
M2012 almost 7 yearsHow can we adjust vertical scroll bar space. Can we do it by using css.
-
shubhamkes almost 7 yearsIt worked for me but now all td is having same width. Which i dont want.
-
Washington Guedes about 6 yearsSimply awesome. Side note:
th:nth-child(2), td:nth-child(2)
is equivalent totr > :nth-child(2)
-
PirateApp almost 6 yearsi want both the first column and the header to be fixed which is not possible as per the data tables compatibility chart
-
A P over 5 years@Roko thank you so much for providing such a clean solution, it works like charm in chrome. Unfortunately, this is not working in IE11. Is there any tweak to make it work in IE11.
-
Roko C. Buljan over 5 years@AP for IE11 (since it does not understands sticky) you could use the second example that uses
translateY
(and the jQuery code - if you don't compile your ES6 js code). It'll work, but will look a bit "jumpish". But that's OK. If one wants to use outdated browsers than such person/company will never complain about choppy things - as far as he's able to see the content and have a decent interaction with your website. Interesting read ahead: nealbuerger.com/2018/01/the-end-of-life-of-internet-explorer-11 -
BENARD Patrick about 5 yearsJust for comment, It works for me in Chrome, the table is at the middle top of the page content.... Can you provide more info ? (I'm on linux)
-
user3191192 almost 5 yearsDoes not work when there is more than one row in the table header. When scrolling the 2nd header line jumps up and lays over the first because they both have "top:0". Tinkered around with making the entire <thead> sticky but could not make it work. Any ideas for multi-line headers (with colspans to boot)?
-
user3191192 almost 5 yearsSpecifically I was referring to the CSS solution. The JS solution works better and both header rows stick. But they both have a minor visual flaw, at least on Chrome 74 - the header border lines are not drawn and rows that scroll behind it become a bit visible. Likewise the top table border also does not get drawn and a pixel of the scrolled table data becomes visible at the top edge of the table.
-
Roko C. Buljan almost 5 years@user3191192 (using JS example) on desktop, Chrome 74 works great for me, only on mobile there's a small hop. Regarding TH
border
, yes, it cannot be drawn by the browser. See my edited answer for a solution usingbox-shadow
. -
user2182349 almost 5 yearsYou can overcome the
table-layout:fixed
by adding a CSS class with JavaScript -
Ivan Koshelev over 4 yearsIf the top solution of
{ position: sticky; top: 0; }
somehow does not work in your code - check theoverflow-x
andoverflow-y
css properties of containing elements, especiallytable
,thead
,tr
. Overflows, surprisingly both of them, must not be set. You may need to explicitlyunset
them. More info - stackoverflow.com/a/54806576/882936 -
ViBoNaCci about 4 yearsposition: sticky is widely underused and a bit counter intuitive, especially since you need the top: 0 as well. But this is actually a perfect case to show how to use position sticky purposefully!
-
shao.lo over 3 yearsI skipped over this one for the much higher rated answer by @Roko C. Buljan below only to find that answer didn't work when the header had multiple rows. This solution works great. Thank you so much!!!
-
blissweb over 3 yearsI did display:block on the tbody and thead and they align perfectly. Block is the key for the scrolling and height to work. Thanks.
-
deanwilliammills over 3 years
th
does not align withtd
-
Michael over 3 yearsDunno if it's because my table is in a nested grid layout or the fact that the table is really wide, but this produces the most bizarre rendering on my table - it's like each column is placed a couple pixels offset to the right so each column nearly overwrites the previous one resulting in a very malfunctioned look
-
izogfif over 3 yearsThis doesn't work if you want to use
<colgroup><col style="width: 10px;"><col style="width: 100px;"></colgroup>
to manage width of columns of your table - all your settings are ignored. -
Jan Turoň about 3 yearsUpvoted with a note that the outer element is not needed (just set
block
to table display, anonymous table wrapper will be created), fixed table layout is not needed andsticky
can't be set on thead and tr in today's browsers (would you add this info to your answer to save others troubles implementing it?) -
Jason Lim Ji Chen about 3 yearsHad issue with this solution on the header row: The header role only capable of having not more than 4 column, and new row will summon if you're having more than 4 columns.*Need to adjust the width % to your corresponding row number*
-
ibexy about 3 yearsHow do you add a horizontal scroll to the css only option?
-
Roko C. Buljan about 3 years@ibexy edited my answer to account for sticky TH columns. Here's a demo: jsfiddle.net/RokoCB/863s5mt4
-
Tom Shelley over 2 yearsgot around the colgroup issue with
.table-scroll th:nth-child(1), .table-scroll td:nth-child(1) { width: 3%; }
for n number of columns -
Mark over 2 yearsWorked like a charm! Thanks. It starts floating when the header goes offscreen. Just what I needed. I did set a background color so its not mixing with the table content and a z-index:2, so its in front
-
AdityaDees about 2 yearsthis not working for
rowspan
header @RokoC.Buljan -
Roko C. Buljan about 2 yearsIt is working: jsfiddle.net/L69ho31p @AdityaDees
-
AdityaDees about 2 yearsi've post the answer about what I mean stackoverflow.com/a/71291701/7590238
-
mplungjan about 2 yearsWould you mind making a minimal reproducible example using the
[<>]
snippet editor? -
Alex Klaus about 2 years@mplungjan, added a code snippet.
-
Steve Horvath about 2 yearsUnlike the top solution, this requires a table height set to lower than the page and an extra inner scrollbar will also appear. Sometimes this is what you want (eg for a fixed-size inner table) but otherwise it's less than ideal.