Toggle Table Data with CSS

The Problem

You need to design a table that displays its data in two different units. You want to avoid displaying both sets at once.

I came across this little problem while working on an e-commerce website. The client wanted to display the measurements in their size guide in both centimetres and inches.

The user shouldn’t have to convert the measurements to their unit of preference. And displaying the measurements in both units at once wasn’t a very desirable option. Why should the user go through both units when they only need to see the one they prefer? It’s our job to improve their experience after all.

The Solution

Give the user the option to toggle between both units. If they want to peruse the size guide in centimetres, they can. If their American friend next to them needs to see the measurements in inches, they are just a click/tap away.

Using JavaScript is an obvious option. And given many online stores rely on JavaScript for critical functions (adding items to cart, checkout process, etc), a non-JavaScript method may not seem necessary here. It is possible nonetheless.

Update: based on Heydon Pickering's tweet, this method is not great for accessibility. Proceed with caution!

The method is really simple. We'll rely on a checkbox's state .mycheckbox:checked and use CSS's general sibling selector to target the measurements we want to hide/display.

Here is what we're after:

See the Pen Toggle Table Data with CSS (size guide example) by Hussein Al Hammad (@hus_hmd) on CodePen.

The Code

A size guide is typically presented in a table. The markup for the table here is pretty standard. We'll just include two spans (one for each measurement unit) inside the table's tds.

In order to be able to target these spans with the general sibling selector and based on the checkbox's state .checkbox:checked ~ table span, we need to make the checkbox and the table siblings (i.e. have the same parent element). We also need to place the checkbox before the table. The checkbox doesn't have to be placed directly before the table though.

"The ~ combinator separates two selectors and matches the second element only if it is preceded by the first, and both share a common parent."

Simplified markup

1<input type="checkbox" id="toggle" />
2<table>
3 <tbody>
4 <tr>
5 <td>Small</td>
6 <td>
7 <span class="default">20 cm</span>
8 <span class="alt">8 inch</span>
9 </td>
10 <td>
11 <span class="default">28 cm</span>
12 <span class="alt">11 inch</span>
13 </td>
14 </tr>
15 </tbody>
16</table>

CSS to toggle spans

1table .default {
2 display: inline;
3}
4 
5table .alt {
6 display: none;
7}
8 
9/*checked*/
10.checkbox:checked ~ table .default {
11 display: none;
12}
13 
14.checkbox:checked ~ table .alt {
15 display: inline;
16}

Now it functions as intended. It's a table with a checkbox that toggles the table's data.

By default: we leave the checkbox unchecked and hide .alt spans with CSS. When checked, we hide the .default spans and display the .alt spans

Now all there's left to do is to customise our checkbox to make it look like a toggle switch and add clear labelling. We want these elements next to each other in this order:

  1. Main label
  2. First (default) measurement unit label
  3. Checkbox/custom toggle switch
  4. Alternative measurement unit label

There are many examples of custom switches on CodePen. That's a good starting place to get some inspiration.

When coding your own remember this:

Firstly, (as far as I know) you can't use pseudo elements on input[type=checkbox]. In my example, I'm using an empty span for my switch.

The other thing to keep in mind is while you want to hide the checkbox you need it to remain clickable. You want the user to be actually clicking the checkbox when they click on the measurement unit label they want to display. The trick is to set the opacity of the checkbox to 0 (makes it invisible, but remains clickable) and absolutely position it to overlap with the measurement units labels. You may also need to set the checkbox's z-index to a higher value to ensure it's on top (and thus remains clickable).

1<label for="toggle" class="toggle__main-label">Measurements in: </label>
2<label for="toggle" class="toggle__option">CM</label>
3<input type="checkbox" class="toggle__checkbox" id="toggle" />
4<span class="toggle__switch"></span>
5<label for="toggle" class="toggle__option">INCHES</label>
1.toggle__main-label {
2 display: inline-block;
3 vertical-align: middle;
4 margin-right: 10px;
5}
6 
7.toggle__checkbox {
8 opacity: 0; /*invisible but it's there (we need to keep it clickable)*/
9 width: 100%;
10 z-index: 99; /*make sure it's on top so it's clickable*/
11 position: absolute;
12 left: 50%;
13 transform: translateX(-50%);
14 cursor: pointer;
15}
16 
17.toggle__option {
18 display: inline-block;
19 vertical-align: middle;
20 user-select: none;
21}
22 
23.toggle__option:first-child {
24 text-align: right;
25 left: 0;
26}
27.toggle__option:last-child {
28 right: 0;
29}
30 
31.toggle__switch {
32 display: inline-block;
33 vertical-align: middle;
34 position: relative;
35 width: 50px;
36 height: 20px;
37 margin: 0 10px;
38}
39 
40/*focus styles for accessibility*/
41.toggle__checkbox:focus ~ .toggle__switch {
42 outline: 1px solid #19ca2a;
43}
44 
45/*base of toggle switch*/
46.toggle__switch:before {
47 content: "";
48 position: absolute;
49 display: block;
50 left: 50%;
51 top: 50%;
52 transform: translate(-50%, -50%);
53 z-index: 1;
54 background-color: #464646;
55 width: 50px;
56 height: 2px;
57}
58 
59/*move to either side of the base based on checkbox's state*/
60.toggle__switch:after {
61 content: "";
62 position: absolute;
63 display: block;
64 background-color: #171717;
65 z-index: 1;
66 
67 width: 20px;
68 height: 7px;
69 left: 50%;
70 top: 50%;
71 transform: translate(
72 -130%,
73 -50%
74 ); /*position to the left side of the switch base*/
75 transition: transform 0.3s ease-out;
76}
77 
78.toggle__checkbox:checked ~ .toggle__switch:after {
79 transform: translate(
80 30%,
81 -50%
82 ); /*position to the right side of the switch base*/
83}

Last Words

If you want to get a bit fancy, you can detect the user’s country (e.g. from user’s IP address with PHP) and display the unit of choice in their country by default.

Javascript is still an option; you could display the measurements in both units by default and hide the alternative with Javascript (if the script loads). This way users who don’t have Javascript enabled will still be able to view both sets of measurements, and those who have it enabled will be able to toggle the measurements as they wish. Progressive Enhancement FTW.

I haven’t done any accessibility testing. The checkbox is still accessible via the keyboard and the focus styles highlights the custom switch. If you think the checkbox isn’t fully accessible and know how to fix that, please share your thoughts.