Sorting a Table with CSS
Importing the Data
In order to create a table, we need data in the form of some sort of array or JSON object. This data might come from an API, a CSV file, or a spreadsheet like Google Sheets. I have several repls with example code for these cases. This blog post walks through using Google Sheets, which might be easiest if you plan to update the table later.
Creating the Flexbox Table
Once we have created the JSON object, we will insert that object into a template for a flexbox table. I will use nunjucks and NodeJS to create the necessary HTML, but you could use other systems that are more comfortable to you.
<div id="table"> <div id="header"> <div class="row"> <span>{{header[0]}}</span> <div class="rowBody"> {% for i in range(1,header.length) %} <label for="col{{i}}">{{header[i]}}</label> {% endfor %} </div> </div> </div> <div id="body"> {% for row in body %} <div class="row row{{loop.index}}"> <span>{{row[0]}}</span> <div class="rowBody"> {% for i in range(1,row.length) %} <span>{{ row[i] }}</span> {% endfor %} </div> </div> {% endfor %} </div> </div>
In the above template, the first row and first column are kept separate so that they are always visible even when some of the data is off the screen. If the table is small enough that every row and/or column will be visible (or there aren't any header rows/columns) then the template can be simplified.
The simplest way to sort a table with CSS is to use the flexbox order property. Assuming you want to sort the rows rather than the columns it is better to flex the rows first. Each row will have the same height so the flex is really only to reorder the rows. It is really hard to have variable sized rows/columns, sorting, and pagination so we will set fixed widths and heights.
If you are going to sort the columns or really want to have the columns to automatically flex their widths based on content, you can do everything based on columns first.
Sorting Vertically
We will assign several variables to each row. The --c1 variable will be the value of the first column in that row, --c2 will be the value of the second column, and so on. These values need to be positive integers, but that easily be arranged with any data type by computing the rank ahead of time.
Look for off-by-one errors since the nunjucks loop index starts at 1 and there may or may not be header rows/columns.
{% for row in body %} .row{{loop.index}} { {% for i in range(1,row.length) %} --c{{i}}: {{row[i]}};{% endfor %} } {% endfor %}
Once all of these variables have been set, we need radio buttons to determine which column is being sorted. When sorting by the first column, the order of each row will be set to --c1. Thus, when the first radio button is checked, we set the order property for the class to var(--c1).
{% for i in range(1,header.length) %} #col{{i}}:checked ~ #body .row { order: var(--c{{i}}); } {% endfor %}
You can set the flex-direction to either row or row-reverse to sort in the desired direction. If sorting in either direction might be needed, then you can either add one button to switch the direction for everything or have two buttons for each column.
The radio button inputs need to be placed somewhere in the DOM where they can cascade to every row (and the selector used above should agree with that placement), but they should probably be hidden. The labels for each button can be placed anywhere and be styled fully, but you probably want the labels to be in the header row. Each input needs a unique id and then each label needs a for attribute that matches.
Sorting Horizontally
You probably don't want to sort tables horizontally, and doing so makes the file larger and much slower. In general, sorting against the array is always a pain because you need to perform many more sorts. If you only need to sort horizontally, just flip the table so that the columns come first.
With that said, it is possible to create a table that can be sorted in either direction with CSS so we will do just that.
{% for i in range(1,topCount+1) %} .col { {% for ii in range(1,leftCount+1) %}--r{{ii}}: {{10000000 - body[ii - 1][i].raw | int}}; {% endfor %} } {% endfor %} {% for i in range(1,leftCount+1) %} #cellLeft:checked ~ div .col { order: var(--r{{i}}); } {% endfor %}
I am subtracting the value of the cell from 10000000 to sort in the correct direction. Sorting by column-reverse should also work if every column is displayed. Each row will be sorted individually but the values will be the same so the ordering will be the same in each row.
Final Product
You can see an implementation here with data about the popularity of different Wikipedia projects in different countries. For more information about creating tables, read my overview here.