Fairly self-explanatory title but I can’t seem to get all the details working at the same time. I’ve tried to lay out my requirements explicitly—along with my attempt at the end—but feel free let me know if more info is required.
Standard HTMLtable tags, such as thead, tfoot & tbody, are preferred as they provide most of my following requests for “free”.
I couldn’t get CSS display: table or display: flex approaches to work, but if you do, I’ll be interested.
Fixed/static/stickythead and tfoot at the top & bottom of the table, respectively!
Flexible column widths that resize dynamically to the size of their cell contents without any fixed/pre-determined widths, whether that be in px or %.
All text should overflow & wrap normally within their cells. Row heights should adjust accordingly. These come standard in a HTML table.
Vertical scrollbar should appear withintbody when max-height is exceeded. This shouldn’t overlap with thead or tfoot, as shown in my “Exemplar” demo.
Here’s my JSFiddle demo that displays 3 different tables, including my target as well as my attempt. It also includes my HTML and CSS code for a normal table populated with an (hopefully) interesting variety of placeholder text and styled to emphasize the consistent cell alignment at any size that comes courtesy of a table.
Table 1: Exemplar (Static Image) is just an image of what I’d like the final product to look like. Notice the positioning of the scrollbar within the body of the table and right next to the contents.
Table 2: Stock Default provides the HTML table template. Try resizing your viewport to observe how incredibly flexible it is.
Table 3: Positioning within Divs uses a combination of: absolute & relative positioning;nested divs within thead & tfoot; and, hard-coded spacing values to fix alignment. It is nowhere near robust enough for any amount of resizing.
I remember playing around with this about 15 years ago and there was no fullproof solution then and its still not possible today (using the table-layout:auto algorithm) although it may be possible with the new css grid but haven’t had time to test yet.
Many thanks for your comprehensive response, wasn’t expecting anything this quickly!
It seems those 15 years old demos have definitely stood the test of time, at least as far as the extent of CSS has grown. Especially the first one—double header—which has a full width tbody and an integrated scrollbar that’s positioned under the thead. As you mentioned though, thead’s cell contents don’t wrap around as one would expect. Plus, tfoot isn’t fixed to the bottom either but scrolls along with tbody instead.
The first CodePen with table-layout: fixed didn’t work out so well for me as the thead and tfoot didn’t resize proportionately to tbody. I assume this is because of the fixed width columns of tbody.
The second Pen you made is extremely close to my dream-come-true! It’s based on standard, semantic HTML table and both thead & tfoot contents resize/wrap accordingly to tbody perfectly. However, the scrollbar appears outside of the table, instead of between thead and tfoot. Furthermore, the scrollbar width is manually hardcoded so it’s inflexible across different OS/browser configurations. As I mentioned previously, I’d prefer CSS-only but I can totally understand that sometimes a touch of jQuery is needed to sort some bits out dynamically. On the other hand, using jQuery to duplicate the table two extra times is quite a performance hog, especially anywhere near large-scale data. These couple of things prevents me to accepting your answer as the complete solution.
Nevertheless, I’ve added your answer to my Pen (shown below) to get a better idea of how closely it fits with my vision shown in Table 1: Exemplar. Once again, many thanks for your response I really appreciate your efforts here.
Note I changed the js in my demo as it duplicated the table again in error.
Yes, that’s a downside of that approach. The only way I can think to overcome this is to have a height for the header and footer that is tall enough to allow the content in the header to wrap. Usually that would not be an issue as header content is not usually full of content like the cells. If we take that approach then you can get close to your desired outcome of a scrollbar inside the header and footer but will also display correctly on the mac where the scrollbar is overlaid when required. We have to lose the borders on the header though because as the header row is slightly longer depending on browser the borders won’t always match up exactly.
Yes, if you have very large tables then that is a downside but I would wager that they would need to be extremely large before a performance hit was noted. On the contrary all other js versions I’ve seen of fixed headers and footers are extremely ‘janky’ to start with as they have to dynamically update the headers and footers while scrolling etc.
The problem with all the pure CSS solutions is that they need to set tbody to block in order to get the vertical scrollbar on tbody but once you do that then there is no way to have a relationship between the table header and footers as they effectively become separate tables.
I do wonder if css grid can supply a solution but I’m guessing you would need nested grids in order to get the scrollbar and then you probably lose the relationship between the parent grid columns. (I’ll have to experiment when I have a few hours free :)).
Maybe @Erik_J or @ronpat have some suggestions as they always like a challenge
It may also be of interest that we ran a quiz on this about 8 years ago at Sitepoint although it was only for a fixed header but makes interesting reading all the same.
This Pen looks even better than the previous demos. Loving the look of the integrated scrollbar within tbody. Like you said, as soon as borders get involved, it becomes obvious that cell alignment is fairly out-of-whack. Even though the thead and tfoot resize & wrap-around as expected, it’s clearly out of sync with tbody while their extended height (to accommodate wrapping) isn’t really ideal; row height should change only when necessary. In this regard, the Pen from your previous post was more on top of this, but I guess that makes sense given the different methods the 2 tables are rendered.
Fully agreed with you on the use of jQuery. There are good reasons why it’s still a popular library over as long as it has existed. Sometimes, CSS-only—even with a dash of vanilla JS—doesn’t quite cut it, especially in these “supposedly tricky” situations.
Just browsed through the winners of the Quiz & now I can see how your original demo came about. But with my request of a perfectly fixed header & footer with internally scrollable table contents, I daresay I’ve arrived upon another quest for the “Holy Grail”! I wonder if Grids will answer my calls as Flex did for the HTML Holy Grail layout… Just hope it’s sooner rather than later
On a somewhat separate note, I’ve been experimenting with the CSS display: table style to see how that factors into this whole debacle (shown as Table 5 on my Pen). From one point of view, it completely throws HTML table semantics out the window, but then again, all of that is retained via the raw CSS. In fact, it’s retained a bit too well since I’ve essentially arrived back to the stock default table but using divs with classes instead of the semantic elements. The issue of display: block or position: absolute plagues this table just as it does on an actual HTML table. At the very least, I did intend on adding hyperlinks from certain cells/rows at a later stage and this provides a very simple way of doing just that using basic anchors wherever I want to place them; something that I didn’t find an easy way of doing with a regular table (especially clickable rows). So it’s not a complete loss!
On a somewhat somewhat separate note, I’ve been thinking about an alternative way to approach my issue with regards to the internal/integrated scrollbar. What if I create a JS function to dynamically determine the width of the scrollbar onLoad then use that value to add extra padding-right to thead & tfoot? That should take care of points 1, 3 & 4, just need to then work out how to best fix their positions in relation to the table. Actually, just realised point 2 is the main crux of the matter which affects/breaks everything else.
Didn’t have much luck with CSS grid as nested grids lose their relationship with the parent. Managed to get a grid version with position:sticky but only Chrome and Firefox support though.
I think for the time being the only full proof solution will be to use js and perhaps a custom scrollbar so that you can control the widths etc. Currently browsers on the mac system and IE edge don’t show scrollbars until you need them and on the mac they overlay the content and don’t take up room .
Note that all the demos above are just proof of concept and untested so there may be drawbacks I haven’t noticed yet.
A couple of years ago, I spent several days (over a period of weeks) mulling over a “spreadsheet” style layout with fixed header and footer but eventually decided that HTML and CSS could not emulate the ability to position header and footer like Excel allows, yet. I haven’t gone back to it since. Maybe I’ll give it another look, but not right now. Sorry.
@PaulOB It’s amazing how those grids look fine on Chrome but completely bonkers on IE!
I think JS (+ jQuery) is definitely needed for the full-proof solution but I think custom scrollbar might be going too deep, given that I’m not sure how that’d help with positioning the scrollbar beside tbody and sandwiched between the fixed thead & tfoot.
Having said that, I found some jQuery custom scrollbar examples that look terrific across all major browsers and might be worth considering, even if only from a visual/aesthetic point of view.
I think using the data-th custom attribute to copy header content into the CSS is a novel idea of doing things. This gets around having to clone the entire table with minimal hit to HTML semantics. I created a JSFiddle demo based on your code to see it in action.
Unfortunately, it still has some flaws such as the non-wrapping table headers and scrollbar outside the table. Also, I’m finding it quite tricky to adapt the CSS to my table’s markup & custom styling due to the way spacing is handled using hardcoded values, but I guess that’s more on me than the code. You can see my attempt at adapting your code here while my target visual can be found on CodePen.
@ronpat I fear the CSS standards haven’t changed much over the last couple of years as far as fixed headers on table go. I thought I wasn’t asking for too much with my 4 requests but I guess I underestimated the complexity involved.
I notice there’s a bug/behaviour in Chrome where your text-transform capitalize is causing a problem. Some of the left column cells are overlapping the next row because you are losing the capital letter of the first word in the th content. The data-th is being capitalized ok but the original content is losing the capital on the first word thus making the content shorter and when some words wrap they cross over into the cell below.
You can solve the bug by including a space after the content in the th.
I re-jigged my JS demo to use vanilla js instead of jquery and I fixed the scroll bar differences between browsers by using a holding div that was set overflow scroll so that the cloned tables are placed inside that div and thus match exactly the original content. (I’m sure the JS could be tidied up a lot but its only half a dozen lines anyway).
It doesn’t matter what size the browsers scrollbars are (or whether they are overlaid when scrolling as on mac systems) as the content will always match up exactly
The fix comes at the expense of an empty div but the table remains untouched and doesn’t need any special formatting. It should work in all modern browsers (ie11+ - maybe older but haven’t tested).
The table headers and footers are fully fluid both in width and height and have no restrictions upon them.
As mentioned earlier there may be overheads in cloning large tables so use at your own risk or test with care. I’m not sure of the accessibility implications either so probably need to mark up with aria:hidden rules.
Standard HTML table & its constituent semantic elements.
Fixed thead & tfoot at the top & bottom of the table, respectively.
All column widths resize automatically depending on cell contents.
☐ Vertical scrollbar within tbody.
@PaulOB This is greatness so far. Even ignoring the last requirement, this is an amazing feat as it is! I have some follow-up questions for when you can spare some time:
Looking at the JS, is there a difference between the 2 functions that’s called? I think one of them is meant to be removed/commented out since they both appear to be identical.
Forgetting the 4th requirement for the moment & focusing on your execution of it, have you taken into account when the table isn’t populated enough to necessitate a scrollbar? When that’s the case, the hidden scrollbar hangs off the end of the table & pushes each column further out of alignment consecutively. When the scrollbar is an overlay, it doesn’t look so iffy but the misalignment is still present.
… and returning to the “vertical scrollbar within tbody” request, I wonder if a slight modification could be made to it in your execution: can the scrollbar have margins at its top & bottom that corresponds to the height of thead & tfoot cut away from it? If that’s possible, the CSS can be adapted so that it’s not obvious whether the scrollbar is overlaid within the table or beside it. Removing borders & merging together background-colors could make it look just as if it’s inside the table, without actually adding any of the headache associated with actually doing that.
I can’t thank everyone enough for their input & insight into this. Really much appreciated
You just caught it when I was playing around and only the first one is needed. The Js could be condensed into one line of js but becomes less readable so I reverted it back to original. If you refresh you should see latest version.
That can be overcome by simply setting a height on the table that matches the other heights we set for the wrap (all 300px). In that way if there is only one row then the whole row stretches from header to footer. If there are two rows they take up 50% and so on until there are more rows than the height allows and then the height expands to allow the scroll automatically.
You could use js detect if the table was taller than the 300px height (or whatever height you have set) and then only add the fixed header and footer but that would complicate it a little I feel and I like the way it works at present and remains fairly simple.
The code is updated so just copy and remove rows etc to see how it behaves.
The problem with moving the thead and tfoot above and below the scrollbar is that you can never know the height of the thead/tfoot unless you constantly monitor them as they will change when the window is resized and content wraps. This would mean tying into the resize event in JS and then every time the screen size is changed you would need to update the thead and tfoot positions accordingly. This then becomes an intensive task for the browser (even with throttling) and leads to the ‘janky’ operation of the whole window when resized and something that will annoy most users. It’s not impossible but something you would want to avoid most of the time unless its something that you can’t do without.