CSS Fixed Table Header


Published: 2015-07-20
Updated: 2018-01-29
Web: https://fritzthecat-blog.blogspot.com/2015/07/css-fixed-table-header.html


There ain't no web developer that never worried about a table with a fixed header. HTML browsers should implement this out of the box. But they don't. We need to do it.

Here is a normal HTML table with a height restricted to 12 em.

OneTwoThree
1 ApplesOrangesPlum
2 ApplesOrangesPlum
3 ApplesOrangesPlum
4 ApplesOrangesPlum
5 ApplesOrangesPlum
6 ApplesOrangesPlum
7 ApplesOrangesPlum
8 ApplesOrangesPlum
9 ApplesOrangesPlum
10 ApplesOrangesPlum
11 ApplesOrangesPlum
12 ApplesOrangesPlum
13 ApplesOrangesPlum
14 ApplesOrangesPlum
15 ApplesOrangesPlum
16 ApplesOrangesPlum
17 ApplesOrangesPlum
18 ApplesOrangesPlum
19 ApplesOrangesPlum
20 ApplesOrangesPlum

Here is the same with fixed header.

One
Two
Three
1 ApplesOrangesPlum
2 ApplesOrangesPlum
3 ApplesOrangesPlum
4 ApplesOrangesPlum
5 ApplesOrangesPlum
6 ApplesOrangesPlum
7 ApplesOrangesPlum
8 ApplesOrangesPlum
9 ApplesOrangesPlum
10 ApplesOrangesPlum
11 ApplesOrangesPlum
12 ApplesOrangesPlum
13 ApplesOrangesPlum
14 ApplesOrangesPlum
15 ApplesOrangesPlum
16 ApplesOrangesPlum
17 ApplesOrangesPlum
18 ApplesOrangesPlum
19 ApplesOrangesPlum
20 ApplesOrangesPlum

Don't see no difference? It's not always the look, sometimes it's the feel :-)
You feel the difference as soon as you scroll down. The fixed header stays at its position while the table content scrolls under it. The non-fixed header scrolls away with the content and is no more visible.

How to achieve this? We now enter the kingdom of absolute and relative positioning :-)

Relative and Absolute Positioning

When you explored my latest Blog you can skip this chapter.

An HTML element that was positioned by CSS position: relative (or absolute, or fixed) is the point of reference for its children with position: absolute.

An HTML element that was positioned by CSS position: absolute will position itself relative to its nearest parent with a position different to static. The relative position is then given by top, left, right, bottom coordinates.

Fix the Header

How can this knowledge be applied to fix a table header?
We need ...

  1. ... a parent div with position: relative that will be the reference point for the absolutely positioned header

  2. ... another parent div to restrict the table-content to a certain height, and restrict the content's overflow to auto, else it won't have a scrollbar (default overflow is visible)

  3. ... to set the header to position: absolute and give it a top: 0 coordinate (default coordinate would be its static position below or beside its predecessor HTML element)

Sounds simple and logical. The header would then go absolutely to its relative parent and stay there, while the table scrolls under it.

Unfortunately the browser's table implementations make it impossible to tear the header cells out of the table and pull it up onto some siding.
And, fortunately, clever people have found a workaround for that:

So, step by step now.

1. The Header Parent

.headerSiding {
position: relative; /* need a non-static position */
padding-top: 1.4em; /* place for the fixed header */
}

The topmost parent is positioned relatively, to be the reference point for absolutely positioned child elements. It prepares a siding for the header by declaring a padding. (This padding might need adjustment for headers that are multi-line.)

  <div class="headerSiding">
....
</div>

2. The Scroll Pane

.scrollPane {
height: 20em; /* without height no scrollbar ever */
overflow: auto; /* show scrollbar when needed */
}

This is a standard scroll pane. By setting the height, and setting overflow: auto, you force scrollbars when the content does not fit into the height. Mind that this is positioned static, NOT relative, else the scrollPane would be the reference point for the table header, not the headerSiding!

  <div class="headerSiding">
<div class="scrollPane">
....
</div>
</div>

3. The Header Cells

.scrollPane th div {
position: absolute; /* pinned to next non-static parent */
top: 0; /* at top of parent */
}

This is the tricky part, nevertheless very short. Simply tell the div elements containing the header cell content to go to top, where a padding was prepared for them. That's all concerning CSS, now we also need to look at the HTML.

<div class="headerSiding">
<div class="scrollPane">

<table>
<thead>
<tr>
<th><div>One</div></th><th><div>Two</div></th><th><div>Three</div></th>
</tr>
</thead>

<tbody>
<tr>
<td>1 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>2 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
....
<tbody>
</table>

</div>
</div>

This shows the wrapping of the th header cell contents into div elements.


No more tricks needed for this. This is the layout part. One of the few pure CSS soutions I can believe in. I also tried this out on complex pages with further relative and absolute parents, it works everywhere.

You can view this example also on my hompage.

Click here to see full source code.

  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<!DOCTYPE HTML>
<html>

<head>
<meta name="viewport" content="width=device-width, initial-scale=1"/>

<title>Fixed table header</title>

<style type="text/css">
.headerSiding {
position: relative; /* need a non-static position */
padding-top: 1.4em; /* place for the fixed header */
}
.scrollPane {
height: 20em; /* without height no scrollbar ever */
overflow: auto; /* show scrollbar when needed */
}
.scrollPane th div {
position: absolute; /* pinned to next non-static parent */
top: 0; /* at top of parent */
}
</style>

</head>

<body style="margin-left: 25%; margin-right: 25%;"> <!-- center contents -->

<h1>Table with CSS-only Fixed Header</h1>

<div class="headerSiding">

<div class="scrollPane">

<table>

<thead>
<tr>
<th><div>One</div></th><th><div>Two</div></th><th><div>Three</div></th>
</tr>
</thead>

<tbody>
<tr>
<td>1 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>2 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>3 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>4 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>5 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>6 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>7 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>8 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>9 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>10 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>11 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>12 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>13 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>14 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>15 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>16 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>17 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>18 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>19 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>20 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>21 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>22 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>23 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>24 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>25 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>26 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
<tr>
<td>27 Apples</td><td>Oranges</td><td>Plum</td>
</tr>
</tbody>

</table>

</div>

</div>

</body>

</html>




ɔ⃝ Fritz Ritzberger, 2015-07-20