Column Header Rotation in CSS

By Jimmy Bonney | July 19, 2013

Wheel

Last month, D-Sight Web was updated with a pretty awaited feature: splitting the work between experts when evaluating alternatives and defining preferences in a decision process. In other words and without going into too much details (for that, read the release announcement), this typically allows for a technical expert to only provide input for technical criteria and for a legal expert to focus on legal criteria – for instance.

Such a change required the possibility to assign users to criteria in a pretty limited space. Indeed, this assignation is done in a modal window and the the basic idea for such assignation is simply to have a table where:

  • The users are displayed in rows
  • The criteria are displayed in the different columns

This is represented in the mock-up below.

Simple Table Assignation

The problem with this representation is the amount of information that can be displayed in one screen, i.e. without having to scroll horizontally. Based on our experiments, passed 8 criteria with “normal” length names, the user needs to scroll. While this might be enough for small projects, it will result in a lot of scrolling for larger ones. Fortunately, with modern browsers supporting CSS 3, it is possible to rotate the headers of the columns and therefore limit the actual width needed to display the header of each column. Below is illustrated the difference between traditional table header and rotated headers. Having rotated the headers allowed us to almost double (depending on the length of the criteria names) the amount of visible information. This is illustrated in the two screenshots below.

Table with usual headers

With usual horizontal headers (here above), we could see a bit more than 8 columns without scrolling, while 45 degrees rotated headers (here under) allows us to see 15 columns.

Table with rotated headers

Below is the code snippet used to achieve this rotation. This has been compiled from the two sources given at the end of the article (so thank you Shane Church and LARS GUNTHER (ITPASTORN)) but be aware that some of the classes below are related to twitter bootstrap.

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
<div class="scrollable-table">
  <table class="table table-striped table-header-rotated">
    <thead>
      <tr>
        <!-- First column header is not rotated -->
        <th></th>
        <!-- Following headers are rotated -->
        <th class="rotate-45"><div><span>Column header 1</span></div></th>
        <th class="rotate-45"><div><span>Column header 2</span></div></th>
        <th class="rotate-45"><div><span>Column header 3</span></div></th>
        <th class="rotate-45"><div><span>Column header 4</span></div></th>
        <th class="rotate-45"><div><span>Column header 5</span></div></th>
        <th class="rotate-45"><div><span>Column header 6</span></div></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th class="row-header">Row header 1</th>
        <td><input checked="checked" name="column1[]" type="radio" value="row1-column1"></td>
        <td><input checked="checked" name="column2[]" type="radio" value="row1-column2"></td>
        <td><input name="column3[]" type="radio" value="row1-column3"></td>
        <td><input name="column4[]" type="radio" value="row1-column4"></td>
        <td><input name="column5[]" type="radio" value="row1-column5"></td>
        <td><input name="column6[]" type="radio" value="row1-column6"></td>
      </tr>
      <tr>
        <th class="row-header">Row header 2</th>
        <td><input name="column1[]" type="radio" value="row2-column1"></td>
        <td><input name="column2[]" type="radio" value="row2-column2"></td>
        <td><input checked="checked" name="column3[]" type="radio" value="row2-column3"></td>
        <td><input checked="checked" name="column4[]" type="radio" value="row2-column4"></td>
        <td><input name="column5[]" type="radio" value="row2-column5"></td>
        <td><input name="column6[]" type="radio" value="row2-column6"></td>
      </tr>
      <tr>
        <th class="row-header">Row header 3</th>
        <td><input name="column1[]" type="radio" value="row3-column1"></td>
        <td><input name="column2[]" type="radio" value="row3-column2"></td>
        <td><input name="column3[]" type="radio" value="row3-column3"></td>
        <td><input name="column4[]" type="radio" value="row3-column4"></td>
        <td><input checked="checked" name="column5[]" type="radio" value="row3-column5"></td>
        <td><input checked="checked" name="column6[]" type="radio" value="row3-column6"></td>
      </tr>
    </tbody>
  </table>
</div>

The HTML is quite straightforward. This is a classic table with two bootstrap classes (table and table-striped) and one custom class allowing us to rotate the header (table-header-rotated). In the table header, each th tag has a specific class (rotate-45) and contains a div tag which in turn contains a span tag where the column title is set.

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
.table-header-rotated th.row-header{
  width: auto;
}

.table-header-rotated td{
  width: 40px;
  border-top: 1px solid #dddddd;
  border-left: 1px solid #dddddd;
  border-right: 1px solid #dddddd;
  vertical-align: middle;
  text-align: center;
}

.table-header-rotated th.rotate-45{
  height: 80px;
  width: 40px;
  min-width: 40px;
  max-width: 40px;
  position: relative;
  vertical-align: bottom;
  padding: 0;
  font-size: 12px;
  line-height: 0.8;
}

.table-header-rotated th.rotate-45 > div{
  position: relative;
  top: 0px;
  left: 40px; /* 80 * tan(45) / 2 = 40 where 80 is the height on the cell and 45 is the transform angle*/
  height: 100%;
  -ms-transform:skew(-45deg,0deg);
  -moz-transform:skew(-45deg,0deg);
  -webkit-transform:skew(-45deg,0deg);
  -o-transform:skew(-45deg,0deg);
  transform:skew(-45deg,0deg);
  overflow: hidden;
  border-left: 1px solid #dddddd;
  border-right: 1px solid #dddddd;
  border-top: 1px solid #dddddd;
}

.table-header-rotated th.rotate-45 span {
  -ms-transform:skew(45deg,0deg) rotate(315deg);
  -moz-transform:skew(45deg,0deg) rotate(315deg);
  -webkit-transform:skew(45deg,0deg) rotate(315deg);
  -o-transform:skew(45deg,0deg) rotate(315deg);
  transform:skew(45deg,0deg) rotate(315deg);
  position: absolute;
  bottom: 30px; /* 40 cos(45) = 28 with an additional 2px margin*/
  left: -25px; /*Because it looked good, but there is probably a mathematical link here as well*/
  display: inline-block;
  // width: 100%;
  width: 85px; /* 80 / cos(45) - 40 cos (45) = 85 where 80 is the height of the cell, 40 the width of the cell and 45 the transform angle*/
  text-align: left;
  // white-space: nowrap; /*whether to display in one line or not*/
}

The important thing to notice in the CSS file is that the height and width properties of the different elements (th, div and span) are related. Defining the height and width in .table-header-rotated th.rotate-45 forces us to be careful in the different values used afterwards in .table-header-rotated th.rotate-45 > div and .table-header-rotated th.rotate-45 span. Whenever possible, I’ve tried to explain where the values are coming from. Most of it is basic trigonometry but sometimes it didn’t feel so direct so I hope this helps.

The code snippets above can also be found on this gist.


For the time being, comments are managed by Disqus, a third-party library. I will eventually replace it with another solution, but the timeline is unclear. Considering the amount of data being loaded, if you would like to view comments or post a comment, click on the button below. For more information about why you see this button, take a look at the following article.