You can’t position: sticky;
a <thead>
. Nor a <tr>
. But you can sticky a <th>
, which means you can make sticky headers inside a regular ol’ <table>
. This is tricky stuff, because if you didn’t know this weird quirk, it would be hard to blame you. It makes way more sense to sticky a parent element like the table header rather than each individual element in a row.
The issue boils down to the fact that stickiness requires position: relative
to work and that doesn’t apply to <thead>
and <tr>
in the CSS 2.1 spec.
There are two very extreme reactions to this, should you need to implement sticky table headers and not be aware of the <th>
workaround.
- Don’t use table markup at all. Instead, use different elements (
<div>
s and whatnot) and other CSS layout methods to replicate the style of a table, but not locked out of usingposition: relative
and creatingposition: sticky
parent elements. - Use table elements, but totally remove all their styling defaults with new
display
values.
The first is dangerous because you aren’t using semantic and accessible elements for the content to be read and navigated. The second is almost the same. You can go that route, but need to be really careful to re-apply semantic roles.
Anyway, none of that matters if you just stick (get it?!) to using a sticky
value on those <th>
elements.
See the Pen
Sticky Table Headers with CSS by Chris Coyier (@chriscoyier)
on CodePen.
It’s probably a bit weird to have table headers as a row in the middle of a table, but it’s just illustrating the idea. I was imagining colored header bars separating players on different sports teams or something.
Anytime I think about data tables, I also think about how tricky it can be to make them responsive. Fortunately, there are a variety of ways, all depending on the best way to group and explore the data in them.
My old cukpu is looking for a plugin that supports tables like this. Because I used Excel
A reminder, folks: don’t forget to add a
scope
attribute to yourth
elements.Also, define a
top
(orbottom
) property for the sticky elements (can’t count the times I couldn’t understand why it didn’t work ♂️).Awesome. Now if could stick the first column ;O)
Here is my solution, it can also make first columns sticky.
I copy parts of outerHTML of various table elements, and put this into the innerHTML of ‘sticky’ table elements. The source of the demo is on GIT https://github.com/napengam/sticky
and a demo to visualize the effect is here http://hgsweb.de/sticky/html/index.html
Regards
I had to do this for work. I had a table with any number (from 5 to 30+) of columns. They wanted the header to be sticky and the first column to be sticky. The only way I could get it to work was to combine 4 tables and make it look like one. The “table header” was actually two tables in a div with overflow hidden. When there was a horizontal scroll I had to translate the non-sticky table of columns over in the opposite direction of the scroll to fake the horizontal scroll. The second sets of tables were located under the “table header” with a min-height of 60vh. This made it appear that the header was sticky but it was just scrolling what looked like the “table rows”. Again I had to match the translation for the one sticky column on scroll just in the y-axis. It was a pain to make, and after being approved for months and in beta the project owners all of the sudden didn’t like the table having its own vertical scroll and couldn’t even remember how badly they wanted that first sticky column. Sigh.
For the first option you could use other elements and use ARIA to apply the semantics and roles. Here’s an example – https://www.w3.org/TR/wai-aria-practices/examples/table/table.html.
There are pros and cons of this technique so should be tested thoroughly but the option is there. This would be way easier to make responsive as well.
This works okay for borderless cells, but there are various browser issues with sticky
<th>
with borders, especially with border-collapse:collapse. The best workaround I’ve found is to use border-collapse:separate with border-spacing:0 then set the borders on just two sides (e.g. top/left) of each<th>
and<td>
.Edge has other issues. Adding this helps, but the borders still disappear behind the content when scrolling:
The browser vendors really need to get together on this and let us use sticky with
<thead>
– to hell with the spec – and make sure borders behave themselves when they do so!Good overview of how to achieve this look.
I’m curious if there are any tricks for dealing with translucent objects that use sticky positioning. I’ve found in previous projects that all headers remain stickied underneath (or in some cases, on top of) the most ‘recent’ header to become sticky. I darkened the translucency and it offered acceptable contrast to thwart the bleed-through of text from below, but still looked janky and made the box-shadow around the header very intense. Perhaps something like
sticky-displace
with optionsover
,under
, andpush
?Is there a pseudo class for when the header is stuck? It would be nice if we could use css to add the box-shadow only when it is stuck.
Thanks a lot man, you literally saved my job !!
Works in Firefox!
You also need to specify a height for this to work. I’ve used the max-height property.
sticky does not work if i give overflow-x auto to table container
Hi Chris, thanks for the article. What should I do if I need two headers sticking to the top – one below the other. Use case:- the first header has static values but the 2 header ( below the first header ) has values that we could apply to all the rows – such as a dropdown select, checkbox( to select all rows ), input values. Simply put the 2nd header is like a “Apply All” feature for a column. How to stick these 2 header rows to the top if the rest of the table is scrollable.
Good Work! Its a good fix for simple application
How can you make the first two top rows sticky?
Its Not working in safari browser version 15
I made mine so the thead -> headers stick to the top, and tbody ones cover the other tbody ones up.
with
If you want to replace both of the sticky, you could put a class on the tr to distinguish which one is which.
example https://codepen.io/stephen-george-west/pen/rNpjXYL