#	Fixed Table Headers

Simple HTML tables can fit well enough on a browser page, but there are times when longer tables can be a pain. Of course, you might ask yourself whether a long table belongs on a web page, but if it does, then it would make sense to make it more manageable.

This article explores two options for creating a table with a fixed header row and a scrollable body.

There is some good news and some bad news. The good news is that we can do this with a minimal amount of CSS in modern browsers.

You have probably guessed that the bad news involves Legacy Browsers. However, with a bit more work, we can achieve this.

The two methods will be:

- Use CSS Flex Box and virtually split the table into two parts, one of which will be fixed and the other of which will scroll
- Use the new `position:sticky` property

##	Sample Table

A simplified version of the sample table is:

```html
<div id="table-container">
	<table id="fixed">
		<thead>
			<tr><th>A</th><th>B</th><th>C</th><th>D</th></tr>
		</thead>
		<tbody>
			<tr><td>apple</td><td>banana</td><td>cherry</td><td>date</td></tr>
			<tr><td>apple</td><td>banana</td><td>cherry</td><td>date</td></tr>
		</tbody>
	</table>
</div>
```

For our purposes, not the following:

- The table is contained inside a container called `div#table-container`; this is only necessary for the second method
- The table itself has in `id` of `fixed`
- The table is properly structured and includes a specific header row inside the `thead` section
- In real life there will be many more rows in the `tbody` section

The idea will be to fix the header row in place, and allow the body to scroll.

##	Using Flex Box

To achieve the effect it self is relatively simple. Most of the effort will be to make the columns look right.

### Fixing the Header

The normal behaviour of a table is to look after the sections, rows and columns automatically. Unfortunately, this also includes resizing the table to fit the contents. We will need to change this by changing the `display` propoerty:

```css
table#fixed {
	display: flex;
	flex-direction: column;
}
```

Apart from breaking the columns, you won’t see anything useful. However, what it has achieved is to treat the two inner elements, `thead` and `tbody` as two separate entities, which is why they have their own column widths.

###	Limiting the `tbody`

The next step is to limit the height of the `tbody` element. We could do this with:

```css
table#fixed>tbody {
	height: 140px;
}
```

You may or may not see an effect.

If you have visible borders around either the table or the containing div, you will see that the height has indeed been set, but that the contents has spilt out. This is the normal behaviour of a container, and it is call its `overflow` property.

To get it working properly, you will need to change the `tbody`’s `overflow` property to contain spillage:

```css
table#fixed>tbody {
	overflow-y: scroll;
}
table#fixed>tbody {
	height: 140px;
}
```

The `overflow-y: scroll` property cause the `tbody` not to show content that doesn’t fit, but to enable a vertical scroll bar for that content.

Apart from the columns looking wrong, we have achieved the result.

###	Fixing the Columns

Because the `thead` and `tbody` are independent, they have their own ideas of what the column widths should be. To fix this, we will have to take control.

There are many ways to set column widths, but using Flex Box allows the sort of flexibility which is normal in tables.

The first step is to set all the rows to use Flex:

```css
table#fixed tr {
	display: flex;
	flex-direction: row;
}
```

You won’t see anything yet. Note that the `flex-direction: row` property is redundant as that is the default; it is included only to make the intention clear.

The next part is a bit tedious:

```css
table#fixed>thead>tr>th:nth-child(1),
table#fixed>tbody>tr>td:nth-child(1) {
	max-width: 25%;
}
```

This means that both the first cell in both the `thead` and the `tbody` will get the property `flex: 1`. You don’t have to qualify the selector quite so specifically. The following will also work:

```css
table#fixed thead th:nth-child(1),
table#fixed tbody td:nth-child(1) {
	max-width: 25%;
}
```

The point is to include the first cell in _both_ the the `thead` and the `tbody`.

The important part is that the value for both should be the same.

####	Column Numbers

Of course, you may not have four columns. In this case, you would need to adjust the `width` property accordingly.

Alternatively, you can use the `flex` property:

```css
table#fixed>thead>tr>th:nth-child(1),
table#fixed>tbody>tr>td:nth-child(1) {
	flex: 1;
}
```

The `flex: 1` property means that it will occupy one part of the total of the row. You can also vary this to take up a larger portion of the space.

You can do the same for the rest:

```css
table#fixed>thead>tr>th:nth-child(2),
table#fixed>tbody>tr>td:nth-child(2) {
	flex: 1;
}
table#fixed>thead>tr>th:nth-child(3),
table#fixed>tbody>tr>td:nth-child(3) {
	flex: 1;
}
table#fixed>thead>tr>th:nth-child(4),
table#fixed>tbody>tr>td:nth-child(4) {
	flex: 1;
}
```

Of course, if you _really_ mean them all to be the same, you could have used:

```css
table#fixed>thead>tr>th:nth-child(1),
table#fixed>tbody>tr>td:nth-child(1),
table#fixed>thead>tr>th:nth-child(2),
table#fixed>tbody>tr>td:nth-child(2),
table#fixed>thead>tr>th:nth-child(3),
table#fixed>tbody>tr>td:nth-child(3),
table#fixed>thead>tr>th:nth-child(4),
table#fixed>tbody>tr>td:nth-child(4) {
	flex: 1;
}
```

####	Data Width

The problem with using either the `width` property or the `flex` property is what happens if the data is too wide for the column:

-	In the case of `max-width` the column will fit, but the data will spill over the edge
- In the case of `flex` the column will readjust to fit, which is what it is supposed to do

The problem is that the data in the `tbody` and in the `thead` may vary, so if the data is too wide for either column, you will get a discrepancy.

At this stage, you will just need to be careful.

###	The Scroll Bar

You will have noticed that the header columns are slightly wider than the body columns. This is because the body has a scroll bar, while the header does not. To fix this, we install a dummy scroll bar for the header:

```css
table#fixed>thead,		/*	Dummy Scroll Bar */
table#fixed>tbody {		/*	Real Scroll Bar */
	overflow-y: scroll;
}
```

The appearance will still be slightly odd: the scroll bar _appears_ to extend to the top of the table, though you can only scroll the lower part.

##	Using `position: sticky`

The `position: sticky` property is a relatively new feature, and is available on all modern browsers, with the predictable exception of Microsoft Internet Explorer. However, this is the end of the good news.

Now for the bad news:

- Safari still requires the `-webkit-` prefix, which is not so bad
- Microsoft & Edge don’t support `position: sticky` on `thead` or `tr`; fortunately there is a workaround

The `position: sticky` property locks an element from scrolling. Although you can use this to fix an element on the screen, you can also fix an element inside a container element, assuming that the rest of the contents would be scrollable.

###	Preliminary CSS

In the sample table, we have wrapped the table inside a `div` element. This was ignored in the previous version, but is required in this case.

```css
div#table-container {
	height: 200px;
	overflow-y: scroll;
}
```

This fixes the size of the container `div`, suppresses the spillage, and allows scrolling.

```css
table#fixed {
	width: 100%;
}
```

To maintain the appearance, we get our table to fill the width of the container `div`. We can make adjustments to the `div` if we like.

###	Fixing the `thead`

In Firefox and Safari, you can add the following:

```css
table#fixed thead {
	position: sticky;			//	Standard, including Firefox
	-webkit-position: sticky;	//	Safari
	top: 0;
}
```

That’s it.

The `position: sticky` property will lock the header in place. The `top: 0` property will ensure that it is locked at the top of the block.

Apart from its simplicity, the best part is the table is never broken up, so we don’t have to worry about fixing the column widths.

###	Chrome & Edge

As stated before, `position: sticky` doesn’t work with `thead` or `tr`. It does, however, work with `th` and `td`. This is slightly risky, as the `thead` is not the only element which might contain headers.

Given this, we can use:

```css
table#fixed>thead th {
	position: sticky;
	top: 0;
}
```

This allows the actual `thead` to wander off with the scroll, but fixes its cells in place.

This will work, but in the sample, you won’t see it properly. This is because the colour of the cells is white, but the background colour was applied to the `thead`, which is disappearing as we scroll. To fix this, we will need to apply the background colour to the cells:

```css
table#fixed>thead th {
	position: sticky;
	top: 0;
	background-color: #666;
}
```

###	Compatibility

To begin with, there is no point in fixing both the `thead` and the contained cells. If you require cross-browser support, you may as well use the Chrome/Edge workaround above, as it also works with Firefox and Safari. One day, we will be able to use the first method on all browsers.

Having said this, this technique is _not_ available with Internet Explorer, which, unfortunately, still hangs on as a supported browser. If you need this additional support you can use the Flex Box method and come back in a few years to this one.
